1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
11 #ifndef LO_CLANG_SHARED_PLUGINS
17 #include <clang/Lex/Lexer.h>
26 : public loplugin::FilteringPlugin
< SalLogAreas
>
29 explicit SalLogAreas( const loplugin::InstantiationData
& data
)
30 : FilteringPlugin(data
), inFunction(nullptr) {}
32 bool preRun() override
{
39 lastSalDetailLogStreamMacro
= SourceLocation();
40 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
44 bool VisitFunctionDecl( const FunctionDecl
* function
);
45 bool VisitCallExpr( const CallExpr
* call
);
47 void checkArea( StringRef area
, SourceLocation location
);
48 void checkAreaSyntax(StringRef area
, SourceLocation location
);
50 const FunctionDecl
* inFunction
;
51 SourceLocation lastSalDetailLogStreamMacro
;
52 std::set
< std::string
> logAreas
;
54 std::string firstSeenLogArea
;
55 SourceLocation firstSeenLocation
;
60 This is a compile check.
62 Check area used in SAL_INFO/SAL_WARN macros against the list in include/sal/log-areas.dox and
63 report if the area is not listed there. The fix is either use a proper area or add it to the list
67 bool SalLogAreas::VisitFunctionDecl( const FunctionDecl
* function
)
69 inFunction
= function
;
73 bool SalLogAreas::VisitCallExpr( const CallExpr
* call
)
75 if( ignoreLocation( call
))
77 const FunctionDecl
* func
= call
->getDirectCallee();
81 if( !( func
->getNumParams() == 5 && func
->getIdentifier() != NULL
82 && ( func
->getName() == "sal_detail_log" || func
->getName() == "log" || func
->getName() == "DbgUnhandledException")) )
85 auto tc
= loplugin::DeclCheck(func
);
86 enum class LogCallKind
{ Sal
, DbgUnhandledException
};
89 if( tc
.Function("sal_detail_log") || tc
.Function("log").Namespace("detail").Namespace("sal").GlobalNamespace() )
91 kind
= LogCallKind::Sal
; // fine
94 else if( tc
.Function("DbgUnhandledException").GlobalNamespace() )
96 kind
= LogCallKind::DbgUnhandledException
; // ok
102 // The SAL_DETAIL_LOG_STREAM macro expands to two calls to sal::detail::log(),
103 // so do not warn repeatedly about the same macro (the area->getLocStart() of all the calls
104 // from the same macro should be the same).
105 if( kind
== LogCallKind::Sal
)
107 SourceLocation expansionLocation
= compiler
.getSourceManager().getExpansionLoc( compat::getBeginLoc(call
));
108 if( expansionLocation
== lastSalDetailLogStreamMacro
)
110 lastSalDetailLogStreamMacro
= expansionLocation
;
112 if( const clang::StringLiteral
* area
= dyn_cast
< clang::StringLiteral
>( call
->getArg( areaArgIndex
)->IgnoreParenImpCasts()))
114 if( area
->getKind() == clang::StringLiteral::Ascii
)
115 checkArea( area
->getBytes(), area
->getExprLoc());
117 report( DiagnosticsEngine::Warning
, "unsupported string literal kind (plugin needs fixing?)",
118 compat::getBeginLoc(area
));
121 if( loplugin::DeclCheck(inFunction
).Function("log").Namespace("detail").Namespace("sal").GlobalNamespace()
122 || loplugin::DeclCheck(inFunction
).Function("sal_detail_logFormat").GlobalNamespace() )
123 return true; // These functions only forward to sal_detail_log, so ok.
124 if( call
->getArg( areaArgIndex
)->isNullPointerConstant( compiler
.getASTContext(),
125 Expr::NPC_ValueDependentIsNotNull
) != Expr::NPCK_NotNull
)
126 { // If the area argument is a null pointer, that is allowed only for SAL_DEBUG.
127 const SourceManager
& source
= compiler
.getSourceManager();
128 for( SourceLocation loc
= compat::getBeginLoc(call
);
130 loc
= compat::getImmediateExpansionRange(source
, loc
).first
)
132 StringRef inMacro
= Lexer::getImmediateMacroName( loc
, source
, compiler
.getLangOpts());
133 if( inMacro
== "SAL_DEBUG" || inMacro
== "SAL_DEBUG_BACKTRACE" )
136 report( DiagnosticsEngine::Warning
, "missing log area",
137 compat::getBeginLoc(call
->getArg( 1 )->IgnoreParenImpCasts()));
140 report( DiagnosticsEngine::Warning
, "cannot analyse log area argument (plugin needs fixing?)",
141 compat::getBeginLoc(call
));
145 void SalLogAreas::checkArea( StringRef area
, SourceLocation location
)
147 if( logAreas
.empty())
149 if( !logAreas
.count( area
))
151 report( DiagnosticsEngine::Warning
, "unknown log area '%0' (check or extend include/sal/log-areas.dox)",
153 checkAreaSyntax(area
, location
);
156 // don't leave this alive by default, generates too many false+
158 if (compiler
.getSourceManager().isInMainFile(location
))
160 auto matchpair
= [this,area
](StringRef p1
, StringRef p2
) {
161 return (area
== p1
&& firstSeenLogArea
== p2
) || (area
== p2
&& firstSeenLogArea
== p1
);
163 // these are "cross-module" log areas
164 if (area
== "i18n" || area
== "lok" || area
== "lok.tiledrendering")
166 // these appear to be cross-file log areas
167 else if ( area
== "chart2"
168 || area
== "oox.cscode" || area
== "oox.csdata"
169 || area
== "slideshow.verbose"
170 || area
== "sc.opencl"
171 || area
== "sc.core.formulagroup"
172 || area
== "sw.pageframe" || area
== "sw.idle" || area
== "sw.level2"
173 || area
== "sw.docappend" || area
== "sw.mailmerge"
175 || area
== "vcl.layout" || area
== "vcl.a11y"
176 || area
== "vcl.gdi.fontmetric" || area
== "vcl.opengl"
177 || area
== "vcl.harfbuzz" || area
== "vcl.eventtesting"
178 || area
== "vcl.schedule" || area
== "vcl.unity"
179 || area
== "xmlsecurity.comp"
182 else if (firstSeenLogArea
== "")
184 firstSeenLogArea
= area
;
185 firstSeenLocation
= location
;
187 // some modules do this deliberately
188 else if (firstSeenLogArea
.compare(0, 3, "jfw") == 0
189 || firstSeenLogArea
.compare(0, 6, "opencl") == 0)
191 // mixing these in the same file seems legitimate
193 matchpair("chart2.pie.label.bestfit", "chart2.pie.label.bestfit.inside")
194 || matchpair("editeng", "editeng.chaining")
195 || matchpair("oox.drawingml", "oox.cscode")
196 || matchpair("oox.drawingml", "oox.drawingml.gradient")
197 || matchpair("sc.core", "sc.core.grouparealistener")
198 || matchpair("sc.orcus", "sc.orcus.condformat")
199 || matchpair("sc.orcus", "sc.orcus.style")
200 || matchpair("sc.orcus", "sc.orcus.autofilter")
201 || matchpair("svx", "svx.chaining")
202 || matchpair("sw.ww8", "sw.ww8.level2")
203 || matchpair("writerfilter", "writerfilter.profile")
206 else if (firstSeenLogArea
!= area
)
208 report( DiagnosticsEngine::Warning
, "two different log areas '%0' and '%1' in the same file?",
209 location
) << firstSeenLogArea
<< area
;
210 report( DiagnosticsEngine::Note
, "first area was seen here",
217 void SalLogAreas::checkAreaSyntax(StringRef area
, SourceLocation location
) {
218 for (std::size_t i
= 0;;) {
219 std::size_t j
= area
.find('.', i
);
220 if (j
== StringRef::npos
) {
226 for (; i
!= j
; ++i
) {
228 if (!((c
>= '0' && c
<= '9') || (c
>= 'a' && c
<= 'z'))) {
232 if (j
== area
.size()) {
239 DiagnosticsEngine::Warning
,
240 "invalid log area syntax '%0'%1 (see include/sal/log.hxx for details)",
242 << area
<< (location
.isValid() ? "" : " in include/sal/log-areas.dox");
245 void SalLogAreas::readLogAreas()
247 std::ifstream
is( SRCDIR
"/include/sal/log-areas.dox" );
252 size_t pos
= line
.find( "@li @c " );
253 if( pos
!= std::string::npos
)
255 pos
+= strlen( "@li @c " );
256 size_t end
= line
.find( ' ', pos
);
258 if( end
== std::string::npos
)
259 area
= line
.substr( pos
);
260 else if( pos
!= end
)
261 area
= line
.substr( pos
, end
- pos
);
262 checkAreaSyntax(area
, SourceLocation());
263 logAreas
.insert(area
);
266 // If you get this error message, you possibly have too old icecream (ICECC_EXTRAFILES is needed).
267 if( logAreas
.empty())
268 report( DiagnosticsEngine::Warning
, "error reading log areas" );
271 static loplugin::Plugin::Registration
< SalLogAreas
> sallogareas( "sallogareas" );
275 #endif // LO_CLANG_SHARED_PLUGINS
277 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */