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.
12 #include "sallogareas.hxx"
16 #include <clang/Lex/Lexer.h>
24 This is a compile check.
26 Check area used in SAL_INFO/SAL_WARN macros against the list in include/sal/log-areas.dox and
27 report if the area is not listed there. The fix is either use a proper area or add it to the list
31 SalLogAreas::SalLogAreas( const InstantiationData
& data
)
32 : FilteringPlugin(data
), inFunction(nullptr)
36 void SalLogAreas::run()
39 lastSalDetailLogStreamMacro
= SourceLocation();
40 TraverseDecl( compiler
.getASTContext().getTranslationUnitDecl());
43 bool SalLogAreas::VisitFunctionDecl( const FunctionDecl
* function
)
45 inFunction
= function
;
49 bool SalLogAreas::VisitCallExpr( const CallExpr
* call
)
51 if( ignoreLocation( call
))
53 const FunctionDecl
* func
= call
->getDirectCallee();
57 if( !( func
->getNumParams() == 5 && func
->getIdentifier() != NULL
58 && ( func
->getName() == "sal_detail_log" || func
->getName() == "log" || func
->getName() == "DbgUnhandledException")) )
61 auto tc
= loplugin::DeclCheck(func
);
62 enum class LogCallKind
{ Sal
, DbgUnhandledException
};
65 if( tc
.Function("sal_detail_log") || tc
.Function("log").Namespace("detail").Namespace("sal").GlobalNamespace() )
67 kind
= LogCallKind::Sal
; // fine
70 else if( tc
.Function("DbgUnhandledException").GlobalNamespace() )
72 kind
= LogCallKind::DbgUnhandledException
; // ok
78 // The SAL_DETAIL_LOG_STREAM macro expands to two calls to sal::detail::log(),
79 // so do not warn repeatedly about the same macro (the area->getLocStart() of all the calls
80 // from the same macro should be the same).
81 if( kind
== LogCallKind::Sal
)
83 SourceLocation expansionLocation
= compiler
.getSourceManager().getExpansionLoc( compat::getBeginLoc(call
));
84 if( expansionLocation
== lastSalDetailLogStreamMacro
)
86 lastSalDetailLogStreamMacro
= expansionLocation
;
88 if( const clang::StringLiteral
* area
= dyn_cast
< clang::StringLiteral
>( call
->getArg( areaArgIndex
)->IgnoreParenImpCasts()))
90 if( area
->getKind() == clang::StringLiteral::Ascii
)
91 checkArea( area
->getBytes(), area
->getExprLoc());
93 report( DiagnosticsEngine::Warning
, "unsupported string literal kind (plugin needs fixing?)",
94 compat::getBeginLoc(area
));
97 if( loplugin::DeclCheck(inFunction
).Function("log").Namespace("detail").Namespace("sal").GlobalNamespace()
98 || loplugin::DeclCheck(inFunction
).Function("sal_detail_logFormat").GlobalNamespace() )
99 return true; // These functions only forward to sal_detail_log, so ok.
100 if( call
->getArg( areaArgIndex
)->isNullPointerConstant( compiler
.getASTContext(),
101 Expr::NPC_ValueDependentIsNotNull
) != Expr::NPCK_NotNull
)
102 { // If the area argument is a null pointer, that is allowed only for SAL_DEBUG.
103 const SourceManager
& source
= compiler
.getSourceManager();
104 for( SourceLocation loc
= compat::getBeginLoc(call
);
106 loc
= compat::getImmediateExpansionRange(source
, loc
).first
)
108 StringRef inMacro
= Lexer::getImmediateMacroName( loc
, source
, compiler
.getLangOpts());
109 if( inMacro
== "SAL_DEBUG" || inMacro
== "SAL_DEBUG_BACKTRACE" )
112 report( DiagnosticsEngine::Warning
, "missing log area",
113 compat::getBeginLoc(call
->getArg( 1 )->IgnoreParenImpCasts()));
116 report( DiagnosticsEngine::Warning
, "cannot analyse log area argument (plugin needs fixing?)",
117 compat::getBeginLoc(call
));
121 void SalLogAreas::checkArea( StringRef area
, SourceLocation location
)
123 if( logAreas
.empty())
125 if( !logAreas
.count( area
))
127 report( DiagnosticsEngine::Warning
, "unknown log area '%0' (check or extend include/sal/log-areas.dox)",
129 checkAreaSyntax(area
, location
);
132 // don't leave this alive by default, generates too many false+
134 if (compiler
.getSourceManager().isInMainFile(location
))
136 auto matchpair
= [this,area
](StringRef p1
, StringRef p2
) {
137 return (area
== p1
&& firstSeenLogArea
== p2
) || (area
== p2
&& firstSeenLogArea
== p1
);
139 // these are "cross-module" log areas
140 if (area
== "i18n" || area
== "lok" || area
== "lok.tiledrendering")
142 // these appear to be cross-file log areas
143 else if ( area
== "chart2"
144 || area
== "oox.cscode" || area
== "oox.csdata"
145 || area
== "slideshow.verbose"
146 || area
== "sc.opencl"
147 || area
== "sc.core.formulagroup"
148 || area
== "sw.pageframe" || area
== "sw.idle" || area
== "sw.level2"
149 || area
== "sw.docappend" || area
== "sw.mailmerge"
151 || area
== "vcl.layout" || area
== "vcl.a11y"
152 || area
== "vcl.gdi.fontmetric" || area
== "vcl.opengl"
153 || area
== "vcl.harfbuzz" || area
== "vcl.eventtesting"
154 || area
== "vcl.schedule" || area
== "vcl.unity"
155 || area
== "xmlsecurity.comp"
158 else if (firstSeenLogArea
== "")
160 firstSeenLogArea
= area
;
161 firstSeenLocation
= location
;
163 // some modules do this deliberately
164 else if (firstSeenLogArea
.compare(0, 3, "jfw") == 0
165 || firstSeenLogArea
.compare(0, 6, "opencl") == 0)
167 // mixing these in the same file seems legitimate
169 matchpair("chart2.pie.label.bestfit", "chart2.pie.label.bestfit.inside")
170 || matchpair("editeng", "editeng.chaining")
171 || matchpair("oox.drawingml", "oox.cscode")
172 || matchpair("oox.drawingml", "oox.drawingml.gradient")
173 || matchpair("sc.core", "sc.core.grouparealistener")
174 || matchpair("sc.orcus", "sc.orcus.condformat")
175 || matchpair("sc.orcus", "sc.orcus.style")
176 || matchpair("sc.orcus", "sc.orcus.autofilter")
177 || matchpair("svx", "svx.chaining")
178 || matchpair("sw.ww8", "sw.ww8.level2")
179 || matchpair("writerfilter", "writerfilter.profile")
182 else if (firstSeenLogArea
!= area
)
184 report( DiagnosticsEngine::Warning
, "two different log areas '%0' and '%1' in the same file?",
185 location
) << firstSeenLogArea
<< area
;
186 report( DiagnosticsEngine::Note
, "first area was seen here",
193 void SalLogAreas::checkAreaSyntax(StringRef area
, SourceLocation location
) {
194 for (std::size_t i
= 0;;) {
195 std::size_t j
= area
.find('.', i
);
196 if (j
== StringRef::npos
) {
202 for (; i
!= j
; ++i
) {
204 if (!((c
>= '0' && c
<= '9') || (c
>= 'a' && c
<= 'z'))) {
208 if (j
== area
.size()) {
215 DiagnosticsEngine::Warning
,
216 "invalid log area syntax '%0'%1 (see include/sal/log.hxx for details)",
218 << area
<< (location
.isValid() ? "" : " in include/sal/log-areas.dox");
221 void SalLogAreas::readLogAreas()
223 std::ifstream
is( SRCDIR
"/include/sal/log-areas.dox" );
228 size_t pos
= line
.find( "@li @c " );
229 if( pos
!= std::string::npos
)
231 pos
+= strlen( "@li @c " );
232 size_t end
= line
.find( ' ', pos
);
234 if( end
== std::string::npos
)
235 area
= line
.substr( pos
);
236 else if( pos
!= end
)
237 area
= line
.substr( pos
, end
- pos
);
238 checkAreaSyntax(area
, SourceLocation());
239 logAreas
.insert(area
);
242 // If you get this error message, you possibly have too old icecream (ICECC_EXTRAFILES is needed).
243 if( logAreas
.empty())
244 report( DiagnosticsEngine::Warning
, "error reading log areas" );
247 static Plugin::Registration
< SalLogAreas
> X( "sallogareas" );
251 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */