bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / sallogareas.cxx
blob02df2793824e195d3b10b2890dcdc8d0873cd315
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * Based on LLVM/Clang.
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
13 #include "plugin.hxx"
14 #include "check.hxx"
15 #include "compat.hxx"
17 #include <clang/Lex/Lexer.h>
19 #include <fstream>
20 #include <set>
22 namespace
25 class SalLogAreas
26 : public loplugin::FilteringPlugin< SalLogAreas >
28 public:
29 explicit SalLogAreas( const loplugin::InstantiationData& data )
30 : FilteringPlugin(data), inFunction(nullptr) {}
32 bool preRun() override {
33 return true;
36 void run() override {
37 if (preRun())
39 lastSalDetailLogStreamMacro = SourceLocation();
40 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
44 bool VisitFunctionDecl( const FunctionDecl* function );
45 bool VisitCallExpr( const CallExpr* call );
46 private:
47 void checkArea( StringRef area, SourceLocation location );
48 void checkAreaSyntax(StringRef area, SourceLocation location);
49 void readLogAreas();
50 const FunctionDecl* inFunction;
51 SourceLocation lastSalDetailLogStreamMacro;
52 std::set< std::string > logAreas;
53 #if 0
54 std::string firstSeenLogArea;
55 SourceLocation firstSeenLocation;
56 #endif
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
64 if appropriate.
67 bool SalLogAreas::VisitFunctionDecl( const FunctionDecl* function )
69 inFunction = function;
70 return true;
73 bool SalLogAreas::VisitCallExpr( const CallExpr* call )
75 if( ignoreLocation( call ))
76 return true;
77 const FunctionDecl* func = call->getDirectCallee();
78 if( !func )
79 return true;
81 if( !( func->getNumParams() == 5 && func->getIdentifier() != NULL
82 && ( func->getName() == "sal_detail_log" || func->getName() == "log" || func->getName() == "DbgUnhandledException")) )
83 return true;
85 auto tc = loplugin::DeclCheck(func);
86 enum class LogCallKind { Sal, DbgUnhandledException};
87 LogCallKind kind;
88 int areaArgIndex;
89 if( tc.Function("sal_detail_log") || tc.Function("log").Namespace("detail").Namespace("sal").GlobalNamespace() )
91 kind = LogCallKind::Sal; // fine
92 areaArgIndex = 1;
94 else if( tc.Function("DbgUnhandledException").GlobalNamespace() )
96 kind = LogCallKind::DbgUnhandledException; // ok
97 areaArgIndex = 3;
99 else
100 return true;
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 )
109 return true;
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());
116 else
117 report( DiagnosticsEngine::Warning, "unsupported string literal kind (plugin needs fixing?)",
118 compat::getBeginLoc(area));
119 return true;
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);
129 loc.isMacroID();
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" )
134 return true; // ok
136 report( DiagnosticsEngine::Warning, "missing log area",
137 compat::getBeginLoc(call->getArg( 1 )->IgnoreParenImpCasts()));
138 return true;
140 report( DiagnosticsEngine::Warning, "cannot analyse log area argument (plugin needs fixing?)",
141 compat::getBeginLoc(call));
142 return true;
145 void SalLogAreas::checkArea( StringRef area, SourceLocation location )
147 if( logAreas.empty())
148 readLogAreas();
149 if( !logAreas.count( area ))
151 report( DiagnosticsEngine::Warning, "unknown log area '%0' (check or extend include/sal/log-areas.dox)",
152 location ) << area;
153 checkAreaSyntax(area, location);
154 return;
156 // don't leave this alive by default, generates too many false+
157 #if 0
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"
174 || area == "sw.uno"
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
192 else if (
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",
211 firstSeenLocation );
214 #endif
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) {
221 j = area.size();
223 if (j == i) {
224 goto bad;
226 for (; i != j; ++i) {
227 auto c = area[i];
228 if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z'))) {
229 goto bad;
232 if (j == area.size()) {
233 return;
235 i = j + 1;
237 bad:
238 report(
239 DiagnosticsEngine::Warning,
240 "invalid log area syntax '%0'%1 (see include/sal/log.hxx for details)",
241 location)
242 << area << (location.isValid() ? "" : " in include/sal/log-areas.dox");
245 void SalLogAreas::readLogAreas()
247 std::ifstream is( SRCDIR "/include/sal/log-areas.dox" );
248 while( is.good())
250 std::string line;
251 getline( is, line );
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 );
257 std::string area;
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" );
273 } // namespace
275 #endif // LO_CLANG_SHARED_PLUGINS
277 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */