office-gobmx/compilerplugins/clang/salcall.cxx
Stephan Bergmann 24e7fe2035 Fix loplugin:salcall
For one, the addressOfSet filtering was only done for member functions, not for
free functions, which caused false positives in the wild like the one discussed
in the comments at <https://gerrit.libreoffice.org/c/core/+/102024/
4#message-36ec6ee09ed5badae93c552b82a90068e65d19e2> "call xDesktop->terminate()
when catching SIGTERM/SIGINT" regarding a free function
`terminationHandlerFunction` passed to `osl_createThread`.

For another, it failed to identify some cases where the address of a function is
taken implicitly, like for `f3` in compilerplugins/clang/test/salcall.cxx.  So
make this plugin reuse the existing `loplugin::FunctionAddress` functionality
(which also meant to get rid of this plugin's two-phase design).

Change-Id: Ie290c63b03825d5288d982bc8701cfb886fc84b4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/104585
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2020-10-21 15:02:42 +02:00

613 lines
26 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "plugin.hxx"
#include "check.hxx"
#include "compat.hxx"
#include "functionaddress.hxx"
#include <algorithm>
#include <cassert>
#include <set>
#include <utility>
#include <vector>
// The SAL_CALL function annotation is only necessary on our outward
// facing C++ ABI, anywhere else it is just cargo-cult.
//
//TODO: To find inconsistencies like
//
// template<typename> struct S { void f(); }; // #1
// template<typename T> void S<T>::f() {} // #2
// template void SAL_CALL S<void>::f();
//
// VisitFunctionDecl would need to also visit explicit instantiations, by letting
// shouldVisitTemplateInstantiations return true and returning from VisitFunctionDecl early iff
// decl->getTemplateSpecializationKind() == TSK_ImplicitInstantiation. However, an instantiated
// FunctionDecl is created in TemplateDeclInstantiator::VisitCXXMethodDecl by copying information
// (including source locations) from the declaration at #1, and later modified in
// Sema::InstantiateFunctionDefinition with some source location information from the definition at
// #2. That means that the source scanning in isSalCallFunction below would be thoroughly confused
// and break. (This happens for both explicit and implicit template instantiations, which is the
// reason why calls to isSalCallFunction make sure to not call it with any FunctionDecls
// representing such template instantiations.)
namespace
{
//static bool startswith(const std::string& rStr, const char* pSubStr)
//{
// return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
//}
CXXMethodDecl const* getTemplateInstantiationPattern(CXXMethodDecl const* decl)
{
auto const p = decl->getTemplateInstantiationPattern();
return p == nullptr ? decl : cast<CXXMethodDecl>(p);
}
class SalCall final : public loplugin::FunctionAddress<loplugin::FilteringRewritePlugin<SalCall>>
{
public:
explicit SalCall(loplugin::InstantiationData const& data)
: FunctionAddress(data)
{
}
virtual void run() override
{
if (TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
{
auto const& addressOfSet = getFunctionsWithAddressTaken();
for (auto const decl : m_decls)
{
if (addressOfSet.find(decl->getCanonicalDecl()) == addressOfSet.end())
{
handleFunctionDecl(decl);
}
}
}
}
bool VisitFunctionDecl(FunctionDecl const*);
private:
void handleFunctionDecl(FunctionDecl const* decl);
bool rewrite(SourceLocation);
bool isSalCallFunction(FunctionDecl const* functionDecl, SourceLocation* pLoc = nullptr);
std::set<FunctionDecl const*> m_decls;
};
bool SalCall::VisitFunctionDecl(FunctionDecl const* decl)
{
if (ignoreLocation(decl))
return true;
// ignore template stuff
if (decl->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate)
return true;
auto recordDecl = dyn_cast<CXXRecordDecl>(decl->getDeclContext());
if (recordDecl
&& (recordDecl->getTemplateSpecializationKind() != TSK_Undeclared
|| recordDecl->isDependentContext()))
{
return true;
}
auto canonicalDecl = decl->getCanonicalDecl();
// ignore UNO implementations
if (isInUnoIncludeFile(
compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocation())))
return true;
SourceLocation rewriteLoc;
SourceLocation rewriteCanonicalLoc;
bool bDeclIsSalCall = isSalCallFunction(decl, &rewriteLoc);
bool bCanonicalDeclIsSalCall = isSalCallFunction(canonicalDecl, &rewriteCanonicalLoc);
// first, check for consistency, so we don't trip ourselves up on Linux, where we normally run the plugin
if (canonicalDecl != decl)
{
if (bCanonicalDeclIsSalCall)
; // this is fine, the actual definition have or not have SAL_CALL, and MSVC is fine with it
else if (bDeclIsSalCall)
{
// not fine
report(DiagnosticsEngine::Warning, "SAL_CALL inconsistency", decl->getLocation())
<< decl->getSourceRange();
report(DiagnosticsEngine::Note, "SAL_CALL inconsistency", canonicalDecl->getLocation())
<< canonicalDecl->getSourceRange();
return true;
}
}
auto methodDecl = dyn_cast<CXXMethodDecl>(canonicalDecl);
if (methodDecl)
{
for (auto iter = methodDecl->begin_overridden_methods();
iter != methodDecl->end_overridden_methods(); ++iter)
{
const CXXMethodDecl* overriddenMethod
= getTemplateInstantiationPattern(*iter)->getCanonicalDecl();
if (bCanonicalDeclIsSalCall != isSalCallFunction(overriddenMethod))
{
report(DiagnosticsEngine::Warning, "SAL_CALL inconsistency",
methodDecl->getLocation())
<< methodDecl->getSourceRange();
report(DiagnosticsEngine::Note, "SAL_CALL inconsistency",
overriddenMethod->getLocation())
<< overriddenMethod->getSourceRange();
return true;
}
}
}
if (!bCanonicalDeclIsSalCall)
return true;
if (!decl->isThisDeclarationADefinition() && !(methodDecl && methodDecl->isPure()))
return true;
m_decls.insert(decl);
return true;
}
void SalCall::handleFunctionDecl(FunctionDecl const* decl)
{
// some base classes are overridden by sub-classes which override both the base-class and a UNO class
if (auto recordDecl = dyn_cast<CXXRecordDecl>(decl->getDeclContext()))
{
auto dc = loplugin::DeclCheck(recordDecl);
if (dc.Class("OProxyAggregation").Namespace("comphelper").GlobalNamespace()
|| dc.Class("OComponentProxyAggregationHelper")
.Namespace("comphelper")
.GlobalNamespace()
|| dc.Class("SvxShapeMaster").GlobalNamespace()
|| dc.Class("ListBoxAccessibleBase").Namespace("accessibility").GlobalNamespace()
|| dc.Class("AsyncEventNotifierBase").Namespace("comphelper").GlobalNamespace()
|| dc.Class("ODescriptor")
.Namespace("sdbcx")
.Namespace("connectivity")
.GlobalNamespace()
|| dc.Class("IController").Namespace("dbaui").GlobalNamespace()
|| dc.Class("ORowSetBase").Namespace("dbaccess").GlobalNamespace()
|| dc.Class("OComponentAdapterBase").Namespace("bib").GlobalNamespace()
|| dc.Class("IEventProcessor").Namespace("comphelper").GlobalNamespace()
|| dc.Class("SvxUnoTextBase").GlobalNamespace()
|| dc.Class("OInterfaceContainer").Namespace("frm").GlobalNamespace()
|| dc.Class("AccessibleComponentBase").Namespace("accessibility").GlobalNamespace()
|| dc.Class("ContextHandler2Helper")
.Namespace("core")
.Namespace("oox")
.GlobalNamespace()
|| dc.Class("AccessibleStaticTextBase").Namespace("accessibility").GlobalNamespace()
|| dc.Class("OCommonPicker").Namespace("svt").GlobalNamespace()
|| dc.Class("VbaDocumentBase").GlobalNamespace()
|| dc.Class("VbaPageSetupBase").GlobalNamespace()
|| dc.Class("ScVbaControl").GlobalNamespace()
)
return;
}
auto canonicalDecl = decl->getCanonicalDecl();
// if any of the overridden methods are SAL_CALL, we should be too
if (auto methodDecl = dyn_cast<CXXMethodDecl>(canonicalDecl))
{
for (auto iter = methodDecl->begin_overridden_methods();
iter != methodDecl->end_overridden_methods(); ++iter)
{
const CXXMethodDecl* overriddenMethod
= getTemplateInstantiationPattern(*iter)->getCanonicalDecl();
if (isSalCallFunction(overriddenMethod))
return;
}
}
SourceLocation rewriteLoc;
SourceLocation rewriteCanonicalLoc;
bool bDeclIsSalCall = isSalCallFunction(decl, &rewriteLoc);
isSalCallFunction(canonicalDecl, &rewriteCanonicalLoc);
bool bOK = rewrite(rewriteLoc);
if (bOK && canonicalDecl != decl)
{
bOK = rewrite(rewriteCanonicalLoc);
}
if (bOK)
return;
if (bDeclIsSalCall)
{
report(DiagnosticsEngine::Warning, "SAL_CALL unnecessary here",
rewriteLoc.isValid() ? rewriteLoc : decl->getLocation())
<< decl->getSourceRange();
}
if (canonicalDecl != decl)
{
report(DiagnosticsEngine::Warning, "SAL_CALL unnecessary here", rewriteCanonicalLoc)
<< canonicalDecl->getSourceRange();
if (!bDeclIsSalCall)
{
report(DiagnosticsEngine::Note, "defined here (without SAL_CALL decoration)",
decl->getLocation())
<< decl->getSourceRange();
}
}
}
//TODO: This doesn't handle all possible cases of macro usage (and possibly never will be able to),
// just what is encountered in practice:
bool SalCall::isSalCallFunction(FunctionDecl const* functionDecl, SourceLocation* pLoc)
{
assert(!functionDecl->isTemplateInstantiation());
//TODO: It appears that FunctionDecls representing explicit template specializations have the
// same issue as those representing (implicit or explicit) instantiations, namely that their
// data (including relevant source locations) is an incoherent combination of data from the
// original template declaration and the later specialization definition. For example, for the
// OValueLimitedType<double>::registerProperties specialization at
// forms/source/xforms/datatyperepository.cxx:241, the FunctionDecl (which is even considered
// canonic) representing the base-class function overridden by ODecimalType::registerProperties
// (forms/source/xforms/datatypes.hxx:299) is dumped as
//
// CXXMethodDecl <forms/source/xforms/datatypes.hxx:217:9, col:54>
// forms/source/xforms/datatyperepository.cxx:242:37 registerProperties 'void (void)' virtual
//
// mixing the source range ("datatypes.hxx:217:9, col:54") from the original declaration with
// the name location ("datatyperepository.cxx:242:37") from the explicit specialization. Just
// give up for now and assume no "SAL_CALL" is present:
if (functionDecl->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
{
return false;
}
SourceManager& SM = compiler.getSourceManager();
std::vector<SourceRange> ranges;
SourceLocation startLoc;
SourceLocation endLoc;
bool noReturnType = isa<CXXConstructorDecl>(functionDecl)
|| isa<CXXDestructorDecl>(functionDecl)
|| isa<CXXConversionDecl>(functionDecl);
bool startAfterReturnType = !noReturnType;
if (startAfterReturnType)
{
// For functions that do have a return type, start searching for "SAL_CALL" after the return
// type (which for SAL_CALL functions on Windows will be an AttributedTypeLoc, which the
// implementation of FunctionDecl::getReturnTypeSourceRange does not take into account, so
// do that here explicitly):
auto const TSI = functionDecl->getTypeSourceInfo();
if (TSI == nullptr)
{
if (isDebugMode())
{
report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #1, needs investigation",
functionDecl->getLocation())
<< functionDecl->getSourceRange();
}
return false;
}
auto TL = TSI->getTypeLoc().IgnoreParens();
if (auto ATL = TL.getAs<AttributedTypeLoc>())
{
TL = ATL.getModifiedLoc();
}
auto const FTL = TL.getAs<FunctionTypeLoc>();
if (!FTL)
{
// Happens when a function declaration uses a typedef for the function type, as in
//
// SAL_JNI_EXPORT javaunohelper::detail::Func_bootstrap
// Java_com_sun_star_comp_helper_Bootstrap_cppuhelper_1bootstrap;
//
// in javaunohelper/source/juhx-export-functions.hxx.
//TODO: check the typedef for mention of "SAL_CALL" (and also check for usage of such
// typedefs in the !startAfterReturnType case below)
return false;
}
startLoc = FTL.getReturnLoc().getEndLoc();
while (SM.isMacroArgExpansion(startLoc, &startLoc))
{
}
// Stop searching for "SAL_CALL" at the start of the function declaration's name (for
// qualified names this will point after the qualifiers, but needlessly including those in
// the search should be harmless---modulo issues with using "SAL_CALL" as the name of a
// function-like macro parameter as discussed below):
endLoc = compat::getBeginLoc(functionDecl->getNameInfo());
while (SM.isMacroArgExpansion(endLoc, &endLoc))
{
}
while (endLoc.isMacroID() && SM.isAtStartOfImmediateMacroExpansion(endLoc, &endLoc))
{
}
endLoc = SM.getSpellingLoc(endLoc);
auto const slEnd = Lexer::getLocForEndOfToken(startLoc, 0, SM, compiler.getLangOpts());
if (slEnd.isValid())
{
// startLoc is either non-macro, or at end of macro; one source range from startLoc to
// endLoc:
startLoc = slEnd;
while (startLoc.isMacroID() && SM.isAtEndOfImmediateMacroExpansion(startLoc, &startLoc))
{
}
startLoc = SM.getSpellingLoc(startLoc);
if (startLoc.isValid() && endLoc.isValid() && startLoc != endLoc
&& !SM.isBeforeInTranslationUnit(startLoc, endLoc))
{
// Happens for uses of trailing return type (in which case starting instead at the
// start of the function declaration should be fine), but also for cases like
//
// void (*f())();
//
// where the function name is within the function type (TODO: in which case starting
// at the start can erroneously pick up the "SAL_CALL" from the returned pointer-to-
// function type in cases like
//
// void SAL_CALL (*f())();
//
// that are hopefully rare):
startAfterReturnType = false;
}
}
else
{
// startLoc is within a macro body; two source ranges, first is the remainder of the
// corresponding macro definition's replacement text, second is from after the macro
// invocation to endLoc, unless endLoc is already in the first range:
//TODO: If the macro is a function-like macro with a parameter named "SAL_CALL", uses of
// that parameter in the remainder of the replacement text will be false positives.
assert(SM.isMacroBodyExpansion(startLoc));
auto const startLoc2 = compat::getImmediateExpansionRange(SM, startLoc).second;
auto name = Lexer::getImmediateMacroName(startLoc, SM, compiler.getLangOpts());
while (name.startswith("\\\n"))
{
name = name.drop_front(2);
while (!name.empty()
&& (name.front() == ' ' || name.front() == '\t' || name.front() == '\n'
|| name.front() == '\v' || name.front() == '\f'))
{
name = name.drop_front(1);
}
}
auto const MI = compiler.getPreprocessor()
.getMacroDefinitionAtLoc(&compiler.getASTContext().Idents.get(name),
SM.getSpellingLoc(startLoc))
.getMacroInfo();
assert(MI != nullptr);
auto endLoc1 = MI->getDefinitionEndLoc();
assert(endLoc1.isFileID());
endLoc1 = Lexer::getLocForEndOfToken(endLoc1, 0, SM, compiler.getLangOpts());
startLoc = Lexer::getLocForEndOfToken(SM.getSpellingLoc(startLoc), 0, SM,
compiler.getLangOpts());
if (!compat::isPointWithin(SM, endLoc, startLoc, endLoc1))
{
ranges.emplace_back(startLoc, endLoc1);
startLoc = Lexer::getLocForEndOfToken(SM.getSpellingLoc(startLoc2), 0, SM,
compiler.getLangOpts());
}
}
}
if (!startAfterReturnType)
{
// Stop searching for "SAL_CALL" at the start of the function declaration's name (for
// qualified names this will point after the qualifiers, but needlessly including those in
// the search should be harmless):
endLoc = compat::getBeginLoc(functionDecl->getNameInfo());
while (endLoc.isMacroID() && SM.isAtStartOfImmediateMacroExpansion(endLoc, &endLoc))
{
}
SourceRange macroRange;
if (SM.isMacroBodyExpansion(endLoc))
{
auto name = Lexer::getImmediateMacroName(endLoc, SM, compiler.getLangOpts());
while (name.startswith("\\\n"))
{
name = name.drop_front(2);
while (!name.empty()
&& (name.front() == ' ' || name.front() == '\t' || name.front() == '\n'
|| name.front() == '\v' || name.front() == '\f'))
{
name = name.drop_front(1);
}
}
auto const MI = compiler.getPreprocessor()
.getMacroDefinitionAtLoc(&compiler.getASTContext().Idents.get(name),
SM.getSpellingLoc(endLoc))
.getMacroInfo();
assert(MI != nullptr);
macroRange = SourceRange(MI->getDefinitionLoc(), MI->getDefinitionEndLoc());
if (isDebugMode() && macroRange.isInvalid())
{
report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #4, needs investigation",
functionDecl->getLocation())
<< functionDecl->getSourceRange();
}
}
#if defined _WIN32
auto const macroExpansion = SM.getExpansionLoc(endLoc);
#endif
endLoc = SM.getSpellingLoc(endLoc);
// Ctors/dtors/conversion functions don't have a return type, start searching for "SAL_CALL"
// at the start of the function declaration:
startLoc = functionDecl->getSourceRange().getBegin();
while (startLoc.isMacroID()
&& !(macroRange.isValid()
&& compat::isPointWithin(SM, SM.getSpellingLoc(startLoc), macroRange.getBegin(),
macroRange.getEnd()))
&& SM.isAtStartOfImmediateMacroExpansion(startLoc, &startLoc))
{
}
#if !defined _WIN32
auto const macroStartLoc = startLoc;
#endif
startLoc = SM.getSpellingLoc(startLoc);
#if defined _WIN32
if (macroRange.isValid()
&& !compat::isPointWithin(SM, startLoc, macroRange.getBegin(), macroRange.getEnd()))
{
// endLoc is within a macro body but startLoc is not; two source ranges, first is from
// startLoc to the macro invocation, second is the leading part of the corresponding
// macro definition's replacement text:
ranges.emplace_back(startLoc, macroExpansion);
startLoc = macroRange.getBegin();
}
#else
// When the SAL_CALL macro expands to nothing, it may even precede the function
// declaration's source range, so go back one token (unless the declaration is known to
// start with a token that must precede a possible "SAL_CALL", like "virtual" or
// "explicit"):
//TODO: this will produce false positives if the declaration is immediately preceded by a
// macro definition whose replacement text ends in "SAL_CALL"
if (noReturnType
&& !(functionDecl->isVirtualAsWritten()
|| (isa<CXXConstructorDecl>(functionDecl)
&& compat::isExplicitSpecified(cast<CXXConstructorDecl>(functionDecl)))
|| (isa<CXXConversionDecl>(functionDecl)
&& compat::isExplicitSpecified(cast<CXXConversionDecl>(functionDecl)))))
{
SourceLocation endLoc1;
if (macroStartLoc.isMacroID()
&& SM.isAtStartOfImmediateMacroExpansion(macroStartLoc, &endLoc1))
{
// startLoc is at the start of a macro body; two source ranges, first one is looking
// backwards one token from the call site of the macro:
auto startLoc1 = endLoc1;
for (;;)
{
startLoc1 = Lexer::GetBeginningOfToken(startLoc1.getLocWithOffset(-1), SM,
compiler.getLangOpts());
auto const s = StringRef(
SM.getCharacterData(startLoc1),
Lexer::MeasureTokenLength(startLoc1, SM, compiler.getLangOpts()));
// When looking backward at least through a function-like macro replacement like
//
// | foo\ |
// | barbaz##X |
//
// starting at "barbaz" in the second line, the next token reported will start at "\"
// in the first line and include the intervening spaces and (part of? looks like an
// error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
// when looking backwards here, without even trying to look at their content:
if (!(s.empty() || s.startswith("/*") || s.startswith("//")
|| s.startswith("\\\n")))
{
break;
}
}
ranges.emplace_back(startLoc1, endLoc1);
}
else
{
for (;;)
{
startLoc = Lexer::GetBeginningOfToken(startLoc.getLocWithOffset(-1), SM,
compiler.getLangOpts());
auto const s = StringRef(
SM.getCharacterData(startLoc),
Lexer::MeasureTokenLength(startLoc, SM, compiler.getLangOpts()));
// When looking backward at least through a function-like macro replacement like
//
// | foo\ |
// | barbaz##X |
//
// starting at "barbaz" in the second line, the next token reported will start at "\"
// in the first line and include the intervening spaces and (part of? looks like an
// error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
// when looking backwards here, without even trying to look at their content:
if (!(s.empty() || s.startswith("/*") || s.startswith("//")
|| s.startswith("\\\n")))
{
break;
}
}
}
}
#endif
}
ranges.emplace_back(startLoc, endLoc);
for (auto const& range : ranges)
{
if (range.isInvalid())
{
if (isDebugMode())
{
report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #2, needs investigation",
functionDecl->getLocation())
<< functionDecl->getSourceRange();
}
return false;
}
if (isDebugMode() && range.getBegin() != range.getEnd()
&& !SM.isBeforeInTranslationUnit(range.getBegin(), range.getEnd()))
{
report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #3, needs investigation",
functionDecl->getLocation())
<< functionDecl->getSourceRange();
}
for (auto loc = range.getBegin(); SM.isBeforeInTranslationUnit(loc, range.getEnd());)
{
unsigned n = Lexer::MeasureTokenLength(loc, SM, compiler.getLangOpts());
auto s = StringRef(compiler.getSourceManager().getCharacterData(loc), n);
while (s.startswith("\\\n"))
{
s = s.drop_front(2);
while (!s.empty()
&& (s.front() == ' ' || s.front() == '\t' || s.front() == '\n'
|| s.front() == '\v' || s.front() == '\f'))
{
s = s.drop_front(1);
}
}
if (s == "SAL_CALL")
{
if (pLoc)
*pLoc = loc;
return true;
}
loc = loc.getLocWithOffset(std::max<unsigned>(n, 1));
}
}
return false;
}
bool SalCall::rewrite(SourceLocation locBegin)
{
if (!rewriter)
return false;
if (!locBegin.isValid())
return false;
auto locEnd = locBegin.getLocWithOffset(8);
if (!locEnd.isValid())
return false;
SourceRange range(locBegin, locEnd);
if (!replaceText(locBegin, 9, ""))
return false;
return true;
}
static loplugin::Plugin::Registration<SalCall> reg("salcall", true);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */