1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
20 // The SAL_CALL function annotation is only necessary on our outward
21 // facing C++ ABI, anywhere else it is just cargo-cult.
24 //TODO: To find inconsistencies like
26 // template<typename> struct S { void f(); }; // #1
27 // template<typename T> void S<T>::f() {} // #2
28 // template void SAL_CALL S<void>::f();
30 // VisitFunctionDecl would need to also visit explicit instantiations, by letting
31 // shouldVisitTemplateInstantiations return true and returning from VisitFunctionDecl early iff
32 // decl->getTemplateSpecializationKind() == TSK_ImplicitInstantiation. However, an instantiatied
33 // FunctionDecl is created in TemplateDeclInstantiator::VisitCXXMethodDecl by copying information
34 // (including source locations) from the declaration at #1, and later modified in
35 // Sema::InstantiateFunctionDefinition with some source location information from the definition at
36 // #2. That means that the source scanning in isSalCallFunction below would be thoroughly confused
37 // and break. (This happens for both explicit and implicit template instantiations, which is the
38 // reason why calls to isSalCallFunction make sure to not call it with any FunctionDecls
39 // representing such template instantiations.)
43 //static bool startswith(const std::string& rStr, const char* pSubStr)
45 // return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
48 CXXMethodDecl
const* getTemplateInstantiationPattern(CXXMethodDecl
const* decl
)
50 auto const p
= decl
->getTemplateInstantiationPattern();
51 return p
== nullptr ? decl
: cast
<CXXMethodDecl
>(p
);
54 class SalCall final
: public loplugin::FilteringRewritePlugin
<SalCall
>
57 explicit SalCall(loplugin::InstantiationData
const& data
)
58 : FilteringRewritePlugin(data
)
62 virtual void run() override
64 m_phase
= PluginPhase::FindAddressOf
;
65 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
66 m_phase
= PluginPhase::Warning
;
67 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
70 bool VisitFunctionDecl(FunctionDecl
const*);
71 bool VisitUnaryAddrOf(UnaryOperator
const*);
72 bool VisitInitListExpr(InitListExpr
const*);
73 bool VisitCallExpr(CallExpr
const*);
74 bool VisitBinAssign(BinaryOperator
const*);
75 bool VisitCXXConstructExpr(CXXConstructExpr
const*);
78 void checkForFunctionDecl(Expr
const*, bool bCheckOnly
= false);
79 bool rewrite(SourceLocation
);
80 bool isSalCallFunction(FunctionDecl
const* functionDecl
, SourceLocation
* pLoc
= nullptr);
82 std::set
<FunctionDecl
const*> m_addressOfSet
;
83 enum class PluginPhase
91 bool SalCall::VisitUnaryAddrOf(UnaryOperator
const* op
)
93 if (m_phase
!= PluginPhase::FindAddressOf
)
95 checkForFunctionDecl(op
->getSubExpr());
99 bool SalCall::VisitBinAssign(BinaryOperator
const* binaryOperator
)
101 if (m_phase
!= PluginPhase::FindAddressOf
)
103 checkForFunctionDecl(binaryOperator
->getRHS());
107 bool SalCall::VisitCallExpr(CallExpr
const* callExpr
)
109 if (m_phase
!= PluginPhase::FindAddressOf
)
111 for (auto arg
: callExpr
->arguments())
112 checkForFunctionDecl(arg
);
116 bool SalCall::VisitCXXConstructExpr(CXXConstructExpr
const* constructExpr
)
118 if (m_phase
!= PluginPhase::FindAddressOf
)
120 for (auto arg
: constructExpr
->arguments())
121 checkForFunctionDecl(arg
);
125 bool SalCall::VisitInitListExpr(InitListExpr
const* initListExpr
)
127 if (m_phase
!= PluginPhase::FindAddressOf
)
129 for (auto subStmt
: *initListExpr
)
130 checkForFunctionDecl(dyn_cast
<Expr
>(subStmt
));
134 void SalCall::checkForFunctionDecl(Expr
const* expr
, bool bCheckOnly
)
136 auto e1
= expr
->IgnoreParenCasts();
137 auto declRef
= dyn_cast
<DeclRefExpr
>(e1
);
140 auto functionDecl
= dyn_cast
<FunctionDecl
>(declRef
->getDecl());
144 getParentStmt(expr
)->dump();
146 m_addressOfSet
.insert(functionDecl
->getCanonicalDecl());
149 bool SalCall::VisitFunctionDecl(FunctionDecl
const* decl
)
151 if (m_phase
!= PluginPhase::Warning
)
153 if (ignoreLocation(decl
))
156 // ignore template stuff
157 if (decl
->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate
)
159 auto recordDecl
= dyn_cast
<CXXRecordDecl
>(decl
->getDeclContext());
161 && (recordDecl
->getTemplateSpecializationKind() != TSK_Undeclared
162 || recordDecl
->isDependentContext()))
167 auto canonicalDecl
= decl
->getCanonicalDecl();
169 // ignore UNO implementations
170 if (isInUnoIncludeFile(
171 compiler
.getSourceManager().getSpellingLoc(canonicalDecl
->getLocation())))
174 SourceLocation rewriteLoc
;
175 SourceLocation rewriteCanonicalLoc
;
176 bool bDeclIsSalCall
= isSalCallFunction(decl
, &rewriteLoc
);
177 bool bCanonicalDeclIsSalCall
= isSalCallFunction(canonicalDecl
, &rewriteCanonicalLoc
);
179 // first, check for consistency, so we don't trip ourselves up on Linux, where we normally run the plugin
180 if (canonicalDecl
!= decl
)
182 if (bCanonicalDeclIsSalCall
)
183 ; // this is fine, the actual definition have or not have SAL_CALL, and MSVC is fine with it
184 else if (bDeclIsSalCall
)
187 report(DiagnosticsEngine::Warning
, "SAL_CALL inconsistency", decl
->getLocation())
188 << decl
->getSourceRange();
189 report(DiagnosticsEngine::Note
, "SAL_CALL inconsistency", canonicalDecl
->getLocation())
190 << canonicalDecl
->getSourceRange();
194 auto methodDecl
= dyn_cast
<CXXMethodDecl
>(canonicalDecl
);
197 for (auto iter
= methodDecl
->begin_overridden_methods();
198 iter
!= methodDecl
->end_overridden_methods(); ++iter
)
200 const CXXMethodDecl
* overriddenMethod
201 = getTemplateInstantiationPattern(*iter
)->getCanonicalDecl();
202 if (bCanonicalDeclIsSalCall
!= isSalCallFunction(overriddenMethod
))
204 report(DiagnosticsEngine::Warning
, "SAL_CALL inconsistency",
205 methodDecl
->getLocation())
206 << methodDecl
->getSourceRange();
207 report(DiagnosticsEngine::Note
, "SAL_CALL inconsistency",
208 overriddenMethod
->getLocation())
209 << overriddenMethod
->getSourceRange();
215 if (!bCanonicalDeclIsSalCall
)
218 if (!decl
->isThisDeclarationADefinition() && !(methodDecl
&& methodDecl
->isPure()))
220 // can only check when we have a definition since this is the most likely time
221 // when the address of the method will be taken
224 if (m_addressOfSet
.find(decl
->getCanonicalDecl()) != m_addressOfSet
.end())
228 // some base classes are overridden by sub-classes which override both the base-class and a UNO class
231 auto dc
= loplugin::DeclCheck(recordDecl
);
232 if (dc
.Class("OProxyAggregation").Namespace("comphelper").GlobalNamespace()
233 || dc
.Class("OComponentProxyAggregationHelper")
234 .Namespace("comphelper")
236 || dc
.Class("SvxShapeMaster").GlobalNamespace()
237 || dc
.Class("ListBoxAccessibleBase").Namespace("accessibility").GlobalNamespace()
238 || dc
.Class("AsyncEventNotifierBase").Namespace("comphelper").GlobalNamespace()
239 || dc
.Class("ODescriptor")
241 .Namespace("connectivity")
243 || dc
.Class("IController").Namespace("dbaui").GlobalNamespace()
244 || dc
.Class("ORowSetBase").Namespace("dbaccess").GlobalNamespace()
245 || dc
.Class("OComponentAdapterBase").Namespace("bib").GlobalNamespace()
246 || dc
.Class("IEventProcessor").Namespace("comphelper").GlobalNamespace()
247 || dc
.Class("SvxUnoTextBase").GlobalNamespace()
248 || dc
.Class("OInterfaceContainer").Namespace("frm").GlobalNamespace()
249 || dc
.Class("AccessibleComponentBase").Namespace("accessibility").GlobalNamespace()
250 || dc
.Class("ContextHandler2Helper")
254 || dc
.Class("AccessibleStaticTextBase").Namespace("accessibility").GlobalNamespace()
255 || dc
.Class("OCommonPicker").Namespace("svt").GlobalNamespace()
256 || dc
.Class("VbaDocumentBase").GlobalNamespace()
257 || dc
.Class("VbaPageSetupBase").GlobalNamespace()
258 || dc
.Class("ScVbaControl").GlobalNamespace()
264 // if any of the overridden methods are SAL_CALL, we should be too
267 for (auto iter
= methodDecl
->begin_overridden_methods();
268 iter
!= methodDecl
->end_overridden_methods(); ++iter
)
270 const CXXMethodDecl
* overriddenMethod
271 = getTemplateInstantiationPattern(*iter
)->getCanonicalDecl();
272 if (isSalCallFunction(overriddenMethod
))
277 bool bOK
= rewrite(rewriteLoc
);
278 if (bOK
&& canonicalDecl
!= decl
)
280 bOK
= rewrite(rewriteCanonicalLoc
);
287 report(DiagnosticsEngine::Warning
, "SAL_CALL unnecessary here",
288 rewriteLoc
.isValid() ? rewriteLoc
: decl
->getLocation())
289 << decl
->getSourceRange();
291 if (canonicalDecl
!= decl
)
293 report(DiagnosticsEngine::Warning
, "SAL_CALL unnecessary here", rewriteCanonicalLoc
)
294 << canonicalDecl
->getSourceRange();
297 report(DiagnosticsEngine::Note
, "defined here (without SAL_CALL decoration)",
299 << decl
->getSourceRange();
306 //TODO: This doesn't handle all possible cases of macro usage (and possibly never will be able to),
307 // just what is encountered in practice:
308 bool SalCall::isSalCallFunction(FunctionDecl
const* functionDecl
, SourceLocation
* pLoc
)
310 assert(!functionDecl
->isTemplateInstantiation());
312 //TODO: It appears that FunctionDecls representing explicit template specializations have the
313 // same issue as those representing (implicit or explicit) instantiations, namely that their
314 // data (including relevant source locations) is an incoherent combination of data from the
315 // original template declaration and the later specialization definition. For example, for the
316 // OValueLimitedType<double>::registerProperties specialization at
317 // forms/source/xforms/datatyperepository.cxx:241, the FunctionDecl (which is even considered
318 // canonic) representing the base-class function overridden by ODecimalType::registerProperties
319 // (forms/source/xforms/datatypes.hxx:299) is dumped as
321 // CXXMethodDecl <forms/source/xforms/datatypes.hxx:217:9, col:54>
322 // forms/source/xforms/datatyperepository.cxx:242:37 registerProperties 'void (void)' virtual
324 // mixing the source range ("datatypes.hxx:217:9, col:54") from the original declaration with
325 // the name location ("datatyperepository.cxx:242:37") from the explicit specialization. Just
326 // give up for now and assume no "SAL_CALL" is present:
327 if (functionDecl
->getTemplateSpecializationKind() == TSK_ExplicitSpecialization
)
332 SourceManager
& SM
= compiler
.getSourceManager();
333 std::vector
<SourceRange
> ranges
;
335 SourceLocation startLoc
;
336 SourceLocation endLoc
;
337 bool noReturnType
= isa
<CXXConstructorDecl
>(functionDecl
)
338 || isa
<CXXDestructorDecl
>(functionDecl
)
339 || isa
<CXXConversionDecl
>(functionDecl
);
340 bool startAfterReturnType
= !noReturnType
;
341 if (startAfterReturnType
)
343 // For functions that do have a return type, start searching for "SAL_CALL" after the return
344 // type (which for SAL_CALL functions on Windows will be an AttributedTypeLoc, which the
345 // implementation of FunctionDecl::getReturnTypeSourceRange does not take into account, so
346 // do that here explicitly):
347 auto const TSI
= functionDecl
->getTypeSourceInfo();
352 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #1, needs investigation",
353 functionDecl
->getLocation())
354 << functionDecl
->getSourceRange();
358 auto TL
= TSI
->getTypeLoc().IgnoreParens();
359 if (auto ATL
= TL
.getAs
<AttributedTypeLoc
>())
361 TL
= ATL
.getModifiedLoc();
363 auto const FTL
= TL
.getAs
<FunctionTypeLoc
>();
366 // Happens when a function declaration uses a typedef for the function type, as in
368 // SAL_JNI_EXPORT javaunohelper::detail::Func_bootstrap
369 // Java_com_sun_star_comp_helper_Bootstrap_cppuhelper_1bootstrap;
371 // in javaunohelper/source/juhx-export-functions.hxx.
372 //TODO: check the typedef for mention of "SAL_CALL" (and also check for usage of such
373 // typedefs in the !startAfterReturnType case below)
376 startLoc
= FTL
.getReturnLoc().getEndLoc();
377 while (SM
.isMacroArgExpansion(startLoc
, &startLoc
))
381 // Stop searching for "SAL_CALL" at the start of the function declaration's name (for
382 // qualified names this will point after the qualifiers, but needlessly including those in
383 // the search should be harmless---modulo issues with using "SAL_CALL" as the name of a
384 // function-like macro parameter as discussed below):
385 endLoc
= compat::getBeginLoc(functionDecl
->getNameInfo());
386 while (SM
.isMacroArgExpansion(endLoc
, &endLoc
))
389 while (endLoc
.isMacroID() && SM
.isAtStartOfImmediateMacroExpansion(endLoc
, &endLoc
))
392 endLoc
= SM
.getSpellingLoc(endLoc
);
394 auto const slEnd
= Lexer::getLocForEndOfToken(startLoc
, 0, SM
, compiler
.getLangOpts());
397 // startLoc is either non-macro, or at end of macro; one source range from startLoc to
400 while (startLoc
.isMacroID() && SM
.isAtEndOfImmediateMacroExpansion(startLoc
, &startLoc
))
403 startLoc
= SM
.getSpellingLoc(startLoc
);
405 if (startLoc
.isValid() && endLoc
.isValid() && startLoc
!= endLoc
406 && !SM
.isBeforeInTranslationUnit(startLoc
, endLoc
))
408 // Happens for uses of trailing return type (in which case starting instead at the
409 // start of the function declaration should be fine), but also for cases like
413 // where the function name is within the function type (TODO: in which case starting
414 // at the start can erroneously pick up the "SAL_CALL" from the returned pointer-to-
415 // function type in cases like
417 // void SAL_CALL (*f())();
419 // that are hopefully rare):
420 startAfterReturnType
= false;
425 // startLoc is within a macro body; two source ranges, first is the remainder of the
426 // corresponding macro definition's replacement text, second is from after the macro
427 // invocation to endLoc, unless endLoc is already in the first range:
428 //TODO: If the macro is a function-like macro with a parameter named "SAL_CALL", uses of
429 // that parameter in the remainder of the replacement text will be false positives.
430 assert(SM
.isMacroBodyExpansion(startLoc
));
431 auto const startLoc2
= compat::getImmediateExpansionRange(SM
, startLoc
).second
;
432 auto name
= Lexer::getImmediateMacroName(startLoc
, SM
, compiler
.getLangOpts());
433 while (name
.startswith("\\\n"))
435 name
= name
.drop_front(2);
437 && (name
.front() == ' ' || name
.front() == '\t' || name
.front() == '\n'
438 || name
.front() == '\v' || name
.front() == '\f'))
440 name
= name
.drop_front(1);
443 auto const MI
= compiler
.getPreprocessor()
444 .getMacroDefinitionAtLoc(&compiler
.getASTContext().Idents
.get(name
),
445 SM
.getSpellingLoc(startLoc
))
447 assert(MI
!= nullptr);
448 auto endLoc1
= MI
->getDefinitionEndLoc();
449 assert(endLoc1
.isFileID());
450 endLoc1
= Lexer::getLocForEndOfToken(endLoc1
, 0, SM
, compiler
.getLangOpts());
451 startLoc
= Lexer::getLocForEndOfToken(SM
.getSpellingLoc(startLoc
), 0, SM
,
452 compiler
.getLangOpts());
453 if (!compat::isPointWithin(SM
, endLoc
, startLoc
, endLoc1
))
455 ranges
.emplace_back(startLoc
, endLoc1
);
456 startLoc
= Lexer::getLocForEndOfToken(SM
.getSpellingLoc(startLoc2
), 0, SM
,
457 compiler
.getLangOpts());
461 if (!startAfterReturnType
)
463 // Stop searching for "SAL_CALL" at the start of the function declaration's name (for
464 // qualified names this will point after the qualifiers, but needlessly including those in
465 // the search should be harmless):
466 endLoc
= compat::getBeginLoc(functionDecl
->getNameInfo());
467 while (endLoc
.isMacroID() && SM
.isAtStartOfImmediateMacroExpansion(endLoc
, &endLoc
))
471 SourceRange macroRange
;
472 if (SM
.isMacroBodyExpansion(endLoc
))
474 auto name
= Lexer::getImmediateMacroName(endLoc
, SM
, compiler
.getLangOpts());
475 while (name
.startswith("\\\n"))
477 name
= name
.drop_front(2);
479 && (name
.front() == ' ' || name
.front() == '\t' || name
.front() == '\n'
480 || name
.front() == '\v' || name
.front() == '\f'))
482 name
= name
.drop_front(1);
485 auto const MI
= compiler
.getPreprocessor()
486 .getMacroDefinitionAtLoc(&compiler
.getASTContext().Idents
.get(name
),
487 SM
.getSpellingLoc(endLoc
))
489 assert(MI
!= nullptr);
490 macroRange
= SourceRange(MI
->getDefinitionLoc(), MI
->getDefinitionEndLoc());
491 if (isDebugMode() && macroRange
.isInvalid())
493 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #4, needs investigation",
494 functionDecl
->getLocation())
495 << functionDecl
->getSourceRange();
500 auto const macroExpansion
= SM
.getExpansionLoc(endLoc
);
502 endLoc
= SM
.getSpellingLoc(endLoc
);
504 // Ctors/dtors/conversion functions don't have a return type, start searching for "SAL_CALL"
505 // at the start of the function declaration:
506 startLoc
= functionDecl
->getSourceRange().getBegin();
507 while (startLoc
.isMacroID()
508 && !(macroRange
.isValid()
509 && compat::isPointWithin(SM
, SM
.getSpellingLoc(startLoc
), macroRange
.getBegin(),
510 macroRange
.getEnd()))
511 && SM
.isAtStartOfImmediateMacroExpansion(startLoc
, &startLoc
))
515 auto const macroStartLoc
= startLoc
;
517 startLoc
= SM
.getSpellingLoc(startLoc
);
520 if (macroRange
.isValid()
521 && !compat::isPointWithin(SM
, startLoc
, macroRange
.getBegin(), macroRange
.getEnd()))
523 // endLoc is within a macro body but startLoc is not; two source ranges, first is from
524 // startLoc to the macro invocation, second is the leading part of the corresponding
525 // macro definition's replacement text:
526 ranges
.emplace_back(startLoc
, macroExpansion
);
527 startLoc
= macroRange
.getBegin();
530 // When the SAL_CALL macro expands to nothing, it may even precede the function
531 // declaration's source range, so go back one token (unless the declaration is known to
532 // start with a token that must precede a possible "SAL_CALL", like "virtual" or
534 //TODO: this will produce false positives if the declaration is immediately preceded by a
535 // macro definition whose replacement text ends in "SAL_CALL"
537 && !(functionDecl
->isVirtualAsWritten()
538 || (isa
<CXXConstructorDecl
>(functionDecl
)
539 && compat::isExplicitSpecified(cast
<CXXConstructorDecl
>(functionDecl
)))
540 || (isa
<CXXConversionDecl
>(functionDecl
)
541 && compat::isExplicitSpecified(cast
<CXXConversionDecl
>(functionDecl
)))))
543 SourceLocation endLoc1
;
544 if (macroStartLoc
.isMacroID()
545 && SM
.isAtStartOfImmediateMacroExpansion(macroStartLoc
, &endLoc1
))
547 // startLoc is at the start of a macro body; two source ranges, first one is looking
548 // backwards one token from the call site of the macro:
549 auto startLoc1
= endLoc1
;
552 startLoc1
= Lexer::GetBeginningOfToken(startLoc1
.getLocWithOffset(-1), SM
,
553 compiler
.getLangOpts());
554 auto const s
= StringRef(
555 SM
.getCharacterData(startLoc1
),
556 Lexer::MeasureTokenLength(startLoc1
, SM
, compiler
.getLangOpts()));
557 // When looking backward at least through a function-like macro replacement like
562 // starting at "barbaz" in the second line, the next token reported will start at "\"
563 // in the first line and include the intervening spaces and (part of? looks like an
564 // error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
565 // when looking backwards here, without even trying to look at their content:
566 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
567 || s
.startswith("\\\n")))
572 ranges
.emplace_back(startLoc1
, endLoc1
);
578 startLoc
= Lexer::GetBeginningOfToken(startLoc
.getLocWithOffset(-1), SM
,
579 compiler
.getLangOpts());
580 auto const s
= StringRef(
581 SM
.getCharacterData(startLoc
),
582 Lexer::MeasureTokenLength(startLoc
, SM
, compiler
.getLangOpts()));
583 // When looking backward at least through a function-like macro replacement like
588 // starting at "barbaz" in the second line, the next token reported will start at "\"
589 // in the first line and include the intervening spaces and (part of? looks like an
590 // error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
591 // when looking backwards here, without even trying to look at their content:
592 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
593 || s
.startswith("\\\n")))
602 ranges
.emplace_back(startLoc
, endLoc
);
604 for (auto const range
: ranges
)
606 if (range
.isInvalid())
610 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #2, needs investigation",
611 functionDecl
->getLocation())
612 << functionDecl
->getSourceRange();
616 if (isDebugMode() && range
.getBegin() != range
.getEnd()
617 && !SM
.isBeforeInTranslationUnit(range
.getBegin(), range
.getEnd()))
619 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #3, needs investigation",
620 functionDecl
->getLocation())
621 << functionDecl
->getSourceRange();
624 for (auto loc
= range
.getBegin(); SM
.isBeforeInTranslationUnit(loc
, range
.getEnd());)
626 unsigned n
= Lexer::MeasureTokenLength(loc
, SM
, compiler
.getLangOpts());
627 auto s
= StringRef(compiler
.getSourceManager().getCharacterData(loc
), n
);
628 while (s
.startswith("\\\n"))
632 && (s
.front() == ' ' || s
.front() == '\t' || s
.front() == '\n'
633 || s
.front() == '\v' || s
.front() == '\f'))
644 loc
= loc
.getLocWithOffset(std::max
<unsigned>(n
, 1));
650 bool SalCall::rewrite(SourceLocation locBegin
)
654 if (!locBegin
.isValid())
657 auto locEnd
= locBegin
.getLocWithOffset(8);
658 if (!locEnd
.isValid())
661 SourceRange
range(locBegin
, locEnd
);
663 if (!replaceText(locBegin
, 9, ""))
669 static loplugin::Plugin::Registration
<SalCall
> reg("salcall", true);
672 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */