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/.
13 #include "functionaddress.hxx"
21 // The SAL_CALL function annotation is only necessary on our outward
22 // facing C++ ABI, anywhere else it is just cargo-cult.
25 //TODO: To find inconsistencies like
27 // template<typename> struct S { void f(); }; // #1
28 // template<typename T> void S<T>::f() {} // #2
29 // template void SAL_CALL S<void>::f();
31 // VisitFunctionDecl would need to also visit explicit instantiations, by letting
32 // shouldVisitTemplateInstantiations return true and returning from VisitFunctionDecl early iff
33 // decl->getTemplateSpecializationKind() == TSK_ImplicitInstantiation. However, an instantiated
34 // FunctionDecl is created in TemplateDeclInstantiator::VisitCXXMethodDecl by copying information
35 // (including source locations) from the declaration at #1, and later modified in
36 // Sema::InstantiateFunctionDefinition with some source location information from the definition at
37 // #2. That means that the source scanning in isSalCallFunction below would be thoroughly confused
38 // and break. (This happens for both explicit and implicit template instantiations, which is the
39 // reason why calls to isSalCallFunction make sure to not call it with any FunctionDecls
40 // representing such template instantiations.)
44 //static bool startswith(const std::string& rStr, const char* pSubStr)
46 // return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
49 CXXMethodDecl
const* getTemplateInstantiationPattern(CXXMethodDecl
const* decl
)
51 auto const p
= decl
->getTemplateInstantiationPattern();
52 return p
== nullptr ? decl
: cast
<CXXMethodDecl
>(p
);
55 class SalCall final
: public loplugin::FunctionAddress
<loplugin::FilteringRewritePlugin
<SalCall
>>
58 explicit SalCall(loplugin::InstantiationData
const& data
)
59 : FunctionAddress(data
)
63 virtual void run() override
65 if (TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
67 auto const& addressOfSet
= getFunctionsWithAddressTaken();
68 for (auto const decl
: m_decls
)
70 if (addressOfSet
.find(decl
->getCanonicalDecl()) == addressOfSet
.end())
72 handleFunctionDecl(decl
);
78 bool VisitFunctionDecl(FunctionDecl
const*);
81 void handleFunctionDecl(FunctionDecl
const* decl
);
82 bool rewrite(SourceLocation
);
83 bool isSalCallFunction(FunctionDecl
const* functionDecl
, SourceLocation
* pLoc
= nullptr);
85 std::set
<FunctionDecl
const*> m_decls
;
88 bool SalCall::VisitFunctionDecl(FunctionDecl
const* decl
)
90 if (ignoreLocation(decl
))
93 // ignore template stuff
94 if (decl
->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate
)
96 auto recordDecl
= dyn_cast
<CXXRecordDecl
>(decl
->getDeclContext());
98 && (recordDecl
->getTemplateSpecializationKind() != TSK_Undeclared
99 || recordDecl
->isDependentContext()))
104 auto canonicalDecl
= decl
->getCanonicalDecl();
106 // ignore UNO implementations
107 if (isInUnoIncludeFile(
108 compiler
.getSourceManager().getSpellingLoc(canonicalDecl
->getLocation())))
111 SourceLocation rewriteLoc
;
112 SourceLocation rewriteCanonicalLoc
;
113 bool bDeclIsSalCall
= isSalCallFunction(decl
, &rewriteLoc
);
114 bool bCanonicalDeclIsSalCall
= isSalCallFunction(canonicalDecl
, &rewriteCanonicalLoc
);
116 // first, check for consistency, so we don't trip ourselves up on Linux, where we normally run the plugin
117 if (canonicalDecl
!= decl
)
119 if (bCanonicalDeclIsSalCall
)
120 ; // this is fine, the actual definition have or not have SAL_CALL, and MSVC is fine with it
121 else if (bDeclIsSalCall
)
124 report(DiagnosticsEngine::Warning
, "SAL_CALL inconsistency", decl
->getLocation())
125 << decl
->getSourceRange();
126 report(DiagnosticsEngine::Note
, "SAL_CALL inconsistency", canonicalDecl
->getLocation())
127 << canonicalDecl
->getSourceRange();
131 auto methodDecl
= dyn_cast
<CXXMethodDecl
>(canonicalDecl
);
134 for (auto iter
= methodDecl
->begin_overridden_methods();
135 iter
!= methodDecl
->end_overridden_methods(); ++iter
)
137 const CXXMethodDecl
* overriddenMethod
138 = getTemplateInstantiationPattern(*iter
)->getCanonicalDecl();
139 if (bCanonicalDeclIsSalCall
!= isSalCallFunction(overriddenMethod
))
141 report(DiagnosticsEngine::Warning
, "SAL_CALL inconsistency",
142 methodDecl
->getLocation())
143 << methodDecl
->getSourceRange();
144 report(DiagnosticsEngine::Note
, "SAL_CALL inconsistency",
145 overriddenMethod
->getLocation())
146 << overriddenMethod
->getSourceRange();
152 if (!bCanonicalDeclIsSalCall
)
155 if (!decl
->isThisDeclarationADefinition() && !(methodDecl
&& compat::isPureVirtual(methodDecl
)))
158 m_decls
.insert(decl
);
162 void SalCall::handleFunctionDecl(FunctionDecl
const* decl
)
164 // some base classes are overridden by sub-classes which override both the base-class and a UNO class
165 if (auto recordDecl
= dyn_cast
<CXXRecordDecl
>(decl
->getDeclContext()))
167 auto dc
= loplugin::DeclCheck(recordDecl
);
168 if (dc
.Class("OProxyAggregation").Namespace("comphelper").GlobalNamespace()
169 || dc
.Class("OComponentProxyAggregationHelper")
170 .Namespace("comphelper")
172 || dc
.Class("SvxShapeMaster").GlobalNamespace()
173 || dc
.Class("ListBoxAccessibleBase").Namespace("accessibility").GlobalNamespace()
174 || dc
.Class("AsyncEventNotifierBase").Namespace("comphelper").GlobalNamespace()
175 || dc
.Class("ODescriptor")
177 .Namespace("connectivity")
179 || dc
.Class("IController").Namespace("dbaui").GlobalNamespace()
180 || dc
.Class("ORowSetBase").Namespace("dbaccess").GlobalNamespace()
181 || dc
.Class("OComponentAdapterBase").Namespace("bib").GlobalNamespace()
182 || dc
.Class("IEventProcessor").Namespace("comphelper").GlobalNamespace()
183 || dc
.Class("SvxUnoTextBase").GlobalNamespace()
184 || dc
.Class("OInterfaceContainer").Namespace("frm").GlobalNamespace()
185 || dc
.Class("AccessibleComponentBase").Namespace("accessibility").GlobalNamespace()
186 || dc
.Class("ContextHandler2Helper")
190 || dc
.Class("AccessibleStaticTextBase").Namespace("accessibility").GlobalNamespace()
191 || dc
.Class("OCommonPicker").Namespace("svt").GlobalNamespace()
192 || dc
.Class("VbaDocumentBase").GlobalNamespace()
193 || dc
.Class("VbaPageSetupBase").GlobalNamespace()
194 || dc
.Class("ScVbaControl").GlobalNamespace()
200 auto canonicalDecl
= decl
->getCanonicalDecl();
202 // if any of the overridden methods are SAL_CALL, we should be too
203 if (auto methodDecl
= dyn_cast
<CXXMethodDecl
>(canonicalDecl
))
205 for (auto iter
= methodDecl
->begin_overridden_methods();
206 iter
!= methodDecl
->end_overridden_methods(); ++iter
)
208 const CXXMethodDecl
* overriddenMethod
209 = getTemplateInstantiationPattern(*iter
)->getCanonicalDecl();
210 if (isSalCallFunction(overriddenMethod
))
215 SourceLocation rewriteLoc
;
216 SourceLocation rewriteCanonicalLoc
;
217 bool bDeclIsSalCall
= isSalCallFunction(decl
, &rewriteLoc
);
218 isSalCallFunction(canonicalDecl
, &rewriteCanonicalLoc
);
220 bool bOK
= rewrite(rewriteLoc
);
221 if (bOK
&& canonicalDecl
!= decl
)
223 bOK
= rewrite(rewriteCanonicalLoc
);
230 report(DiagnosticsEngine::Warning
, "SAL_CALL unnecessary here",
231 rewriteLoc
.isValid() ? rewriteLoc
: decl
->getLocation())
232 << decl
->getSourceRange();
234 if (canonicalDecl
!= decl
)
236 report(DiagnosticsEngine::Warning
, "SAL_CALL unnecessary here", rewriteCanonicalLoc
)
237 << canonicalDecl
->getSourceRange();
240 report(DiagnosticsEngine::Note
, "defined here (without SAL_CALL decoration)",
242 << decl
->getSourceRange();
247 //TODO: This doesn't handle all possible cases of macro usage (and possibly never will be able to),
248 // just what is encountered in practice:
249 bool SalCall::isSalCallFunction(FunctionDecl
const* functionDecl
, SourceLocation
* pLoc
)
251 assert(!functionDecl
->isTemplateInstantiation());
253 //TODO: It appears that FunctionDecls representing explicit template specializations have the
254 // same issue as those representing (implicit or explicit) instantiations, namely that their
255 // data (including relevant source locations) is an incoherent combination of data from the
256 // original template declaration and the later specialization definition. For example, for the
257 // OValueLimitedType<double>::registerProperties specialization at
258 // forms/source/xforms/datatyperepository.cxx:241, the FunctionDecl (which is even considered
259 // canonic) representing the base-class function overridden by ODecimalType::registerProperties
260 // (forms/source/xforms/datatypes.hxx:299) is dumped as
262 // CXXMethodDecl <forms/source/xforms/datatypes.hxx:217:9, col:54>
263 // forms/source/xforms/datatyperepository.cxx:242:37 registerProperties 'void (void)' virtual
265 // mixing the source range ("datatypes.hxx:217:9, col:54") from the original declaration with
266 // the name location ("datatyperepository.cxx:242:37") from the explicit specialization. Just
267 // give up for now and assume no "SAL_CALL" is present:
268 if (functionDecl
->getTemplateSpecializationKind() == TSK_ExplicitSpecialization
)
273 SourceManager
& SM
= compiler
.getSourceManager();
274 std::vector
<SourceRange
> ranges
;
276 SourceLocation startLoc
;
277 SourceLocation endLoc
;
278 bool noReturnType
= isa
<CXXConstructorDecl
>(functionDecl
)
279 || isa
<CXXDestructorDecl
>(functionDecl
)
280 || isa
<CXXConversionDecl
>(functionDecl
);
281 bool startAfterReturnType
= !noReturnType
;
282 if (startAfterReturnType
)
284 // For functions that do have a return type, start searching for "SAL_CALL" after the return
285 // type (which for SAL_CALL functions on Windows will be an AttributedTypeLoc, which the
286 // implementation of FunctionDecl::getReturnTypeSourceRange does not take into account, so
287 // do that here explicitly):
288 auto const TSI
= functionDecl
->getTypeSourceInfo();
293 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #1, needs investigation",
294 functionDecl
->getLocation())
295 << functionDecl
->getSourceRange();
299 auto TL
= TSI
->getTypeLoc().IgnoreParens();
300 if (auto ATL
= TL
.getAs
<AttributedTypeLoc
>())
302 TL
= ATL
.getModifiedLoc();
304 auto const FTL
= TL
.getAs
<FunctionTypeLoc
>();
307 // Happens when a function declaration uses a typedef for the function type, as in
309 // SAL_JNI_EXPORT javaunohelper::detail::Func_bootstrap
310 // Java_com_sun_star_comp_helper_Bootstrap_cppuhelper_1bootstrap;
312 // in javaunohelper/source/juhx-export-functions.hxx.
313 //TODO: check the typedef for mention of "SAL_CALL" (and also check for usage of such
314 // typedefs in the !startAfterReturnType case below)
317 startLoc
= FTL
.getReturnLoc().getEndLoc();
318 while (SM
.isMacroArgExpansion(startLoc
, &startLoc
))
322 // Stop searching for "SAL_CALL" at the start of the function declaration's name (for
323 // qualified names this will point after the qualifiers, but needlessly including those in
324 // the search should be harmless---modulo issues with using "SAL_CALL" as the name of a
325 // function-like macro parameter as discussed below):
326 endLoc
= functionDecl
->getNameInfo().getBeginLoc();
327 while (SM
.isMacroArgExpansion(endLoc
, &endLoc
))
330 while (endLoc
.isMacroID() && SM
.isAtStartOfImmediateMacroExpansion(endLoc
, &endLoc
))
333 endLoc
= SM
.getSpellingLoc(endLoc
);
335 auto const slEnd
= Lexer::getLocForEndOfToken(startLoc
, 0, SM
, compiler
.getLangOpts());
338 // startLoc is either non-macro, or at end of macro; one source range from startLoc to
341 while (startLoc
.isMacroID() && SM
.isAtEndOfImmediateMacroExpansion(startLoc
, &startLoc
))
344 startLoc
= SM
.getSpellingLoc(startLoc
);
346 if (startLoc
.isValid() && endLoc
.isValid() && startLoc
!= endLoc
347 && !SM
.isBeforeInTranslationUnit(startLoc
, endLoc
))
349 // Happens for uses of trailing return type (in which case starting instead at the
350 // start of the function declaration should be fine), but also for cases like
354 // where the function name is within the function type (TODO: in which case starting
355 // at the start can erroneously pick up the "SAL_CALL" from the returned pointer-to-
356 // function type in cases like
358 // void SAL_CALL (*f())();
360 // that are hopefully rare):
361 startAfterReturnType
= false;
366 // startLoc is within a macro body; two source ranges, first is the remainder of the
367 // corresponding macro definition's replacement text, second is from after the macro
368 // invocation to endLoc, unless endLoc is already in the first range:
369 //TODO: If the macro is a function-like macro with a parameter named "SAL_CALL", uses of
370 // that parameter in the remainder of the replacement text will be false positives.
371 assert(SM
.isMacroBodyExpansion(startLoc
));
372 auto const startLoc2
= compat::getImmediateExpansionRange(SM
, startLoc
).second
;
373 auto name
= Lexer::getImmediateMacroName(startLoc
, SM
, compiler
.getLangOpts());
374 while (compat::starts_with(name
, "\\\n"))
376 name
= name
.drop_front(2);
378 && (name
.front() == ' ' || name
.front() == '\t' || name
.front() == '\n'
379 || name
.front() == '\v' || name
.front() == '\f'))
381 name
= name
.drop_front(1);
384 auto const MI
= compiler
.getPreprocessor()
385 .getMacroDefinitionAtLoc(&compiler
.getASTContext().Idents
.get(name
),
386 SM
.getSpellingLoc(startLoc
))
388 assert(MI
!= nullptr);
389 auto endLoc1
= MI
->getDefinitionEndLoc();
390 assert(endLoc1
.isFileID());
391 endLoc1
= Lexer::getLocForEndOfToken(endLoc1
, 0, SM
, compiler
.getLangOpts());
392 startLoc
= Lexer::getLocForEndOfToken(SM
.getSpellingLoc(startLoc
), 0, SM
,
393 compiler
.getLangOpts());
394 if (!SM
.isPointWithin(endLoc
, startLoc
, endLoc1
))
396 ranges
.emplace_back(startLoc
, endLoc1
);
397 startLoc
= Lexer::getLocForEndOfToken(SM
.getSpellingLoc(startLoc2
), 0, SM
,
398 compiler
.getLangOpts());
402 if (!startAfterReturnType
)
404 // Stop searching for "SAL_CALL" at the start of the function declaration's name (for
405 // qualified names this will point after the qualifiers, but needlessly including those in
406 // the search should be harmless):
407 endLoc
= functionDecl
->getNameInfo().getBeginLoc();
408 while (endLoc
.isMacroID() && SM
.isAtStartOfImmediateMacroExpansion(endLoc
, &endLoc
))
412 SourceRange macroRange
;
413 if (SM
.isMacroBodyExpansion(endLoc
))
415 auto name
= Lexer::getImmediateMacroName(endLoc
, SM
, compiler
.getLangOpts());
416 while (compat::starts_with(name
, "\\\n"))
418 name
= name
.drop_front(2);
420 && (name
.front() == ' ' || name
.front() == '\t' || name
.front() == '\n'
421 || name
.front() == '\v' || name
.front() == '\f'))
423 name
= name
.drop_front(1);
426 auto const MI
= compiler
.getPreprocessor()
427 .getMacroDefinitionAtLoc(&compiler
.getASTContext().Idents
.get(name
),
428 SM
.getSpellingLoc(endLoc
))
430 assert(MI
!= nullptr);
431 macroRange
= SourceRange(MI
->getDefinitionLoc(), MI
->getDefinitionEndLoc());
432 if (isDebugMode() && macroRange
.isInvalid())
434 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #4, needs investigation",
435 functionDecl
->getLocation())
436 << functionDecl
->getSourceRange();
441 auto const macroExpansion
= SM
.getExpansionLoc(endLoc
);
443 endLoc
= SM
.getSpellingLoc(endLoc
);
445 // Ctors/dtors/conversion functions don't have a return type, start searching for "SAL_CALL"
446 // at the start of the function declaration:
447 startLoc
= functionDecl
->getSourceRange().getBegin();
448 while (startLoc
.isMacroID()
449 && !(macroRange
.isValid()
450 && SM
.isPointWithin(SM
.getSpellingLoc(startLoc
), macroRange
.getBegin(),
451 macroRange
.getEnd()))
452 && SM
.isAtStartOfImmediateMacroExpansion(startLoc
, &startLoc
))
456 auto const macroStartLoc
= startLoc
;
458 startLoc
= SM
.getSpellingLoc(startLoc
);
461 if (macroRange
.isValid()
462 && !SM
.isPointWithin(startLoc
, macroRange
.getBegin(), macroRange
.getEnd()))
464 // endLoc is within a macro body but startLoc is not; two source ranges, first is from
465 // startLoc to the macro invocation, second is the leading part of the corresponding
466 // macro definition's replacement text:
467 ranges
.emplace_back(startLoc
, macroExpansion
);
468 startLoc
= macroRange
.getBegin();
471 // When the SAL_CALL macro expands to nothing, it may even precede the function
472 // declaration's source range, so go back one token (unless the declaration is known to
473 // start with a token that must precede a possible "SAL_CALL", like "virtual" or
475 //TODO: this will produce false positives if the declaration is immediately preceded by a
476 // macro definition whose replacement text ends in "SAL_CALL"
478 && !(functionDecl
->isVirtualAsWritten()
479 || (isa
<CXXConstructorDecl
>(functionDecl
)
480 && cast
<CXXConstructorDecl
>(functionDecl
)->getExplicitSpecifier().isExplicit())
481 || (isa
<CXXConversionDecl
>(functionDecl
)
482 && cast
<CXXConversionDecl
>(functionDecl
)
483 ->getExplicitSpecifier()
486 SourceLocation endLoc1
;
487 if (macroStartLoc
.isMacroID()
488 && SM
.isAtStartOfImmediateMacroExpansion(macroStartLoc
, &endLoc1
))
490 // startLoc is at the start of a macro body; two source ranges, first one is looking
491 // backwards one token from the call site of the macro:
492 auto startLoc1
= endLoc1
;
495 startLoc1
= Lexer::GetBeginningOfToken(startLoc1
.getLocWithOffset(-1), SM
,
496 compiler
.getLangOpts());
497 auto const s
= StringRef(
498 SM
.getCharacterData(startLoc1
),
499 Lexer::MeasureTokenLength(startLoc1
, SM
, compiler
.getLangOpts()));
500 // When looking backward at least through a function-like macro replacement like
505 // starting at "barbaz" in the second line, the next token reported will start at "\"
506 // in the first line and include the intervening spaces and (part of? looks like an
507 // error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
508 // when looking backwards here, without even trying to look at their content:
509 if (!(s
.empty() || compat::starts_with(s
, "/*") || compat::starts_with(s
, "//")
510 || compat::starts_with(s
, "\\\n")))
515 ranges
.emplace_back(startLoc1
, endLoc1
);
521 startLoc
= Lexer::GetBeginningOfToken(startLoc
.getLocWithOffset(-1), SM
,
522 compiler
.getLangOpts());
523 auto const s
= StringRef(
524 SM
.getCharacterData(startLoc
),
525 Lexer::MeasureTokenLength(startLoc
, SM
, compiler
.getLangOpts()));
526 // When looking backward at least through a function-like macro replacement like
531 // starting at "barbaz" in the second line, the next token reported will start at "\"
532 // in the first line and include the intervening spaces and (part of? looks like an
533 // error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
534 // when looking backwards here, without even trying to look at their content:
535 if (!(s
.empty() || compat::starts_with(s
, "/*") || compat::starts_with(s
, "//")
536 || compat::starts_with(s
, "\\\n")))
545 ranges
.emplace_back(startLoc
, endLoc
);
547 for (auto const& range
: ranges
)
549 if (range
.isInvalid())
553 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #2, needs investigation",
554 functionDecl
->getLocation())
555 << functionDecl
->getSourceRange();
559 if (isDebugMode() && range
.getBegin() != range
.getEnd()
560 && !SM
.isBeforeInTranslationUnit(range
.getBegin(), range
.getEnd()))
562 report(DiagnosticsEngine::Fatal
, "TODO: unexpected failure #3, needs investigation",
563 functionDecl
->getLocation())
564 << functionDecl
->getSourceRange();
567 for (auto loc
= range
.getBegin(); SM
.isBeforeInTranslationUnit(loc
, range
.getEnd());)
569 unsigned n
= Lexer::MeasureTokenLength(loc
, SM
, compiler
.getLangOpts());
570 auto s
= StringRef(compiler
.getSourceManager().getCharacterData(loc
), n
);
571 while (compat::starts_with(s
, "\\\n"))
575 && (s
.front() == ' ' || s
.front() == '\t' || s
.front() == '\n'
576 || s
.front() == '\v' || s
.front() == '\f'))
587 loc
= loc
.getLocWithOffset(std::max
<unsigned>(n
, 1));
593 bool SalCall::rewrite(SourceLocation locBegin
)
597 if (!locBegin
.isValid())
600 auto locEnd
= locBegin
.getLocWithOffset(8);
601 if (!locEnd
.isValid())
604 SourceRange
range(locBegin
, locEnd
);
606 if (!replaceText(locBegin
, 9, ""))
612 static loplugin::Plugin::Registration
<SalCall
> reg("salcall", true);
615 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */