diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /compilerplugins/clang/salcall.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compilerplugins/clang/salcall.cxx')
-rw-r--r-- | compilerplugins/clang/salcall.cxx | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/compilerplugins/clang/salcall.cxx b/compilerplugins/clang/salcall.cxx new file mode 100644 index 000000000..d2b4fc2bf --- /dev/null +++ b/compilerplugins/clang/salcall.cxx @@ -0,0 +1,615 @@ +/* -*- 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 = functionDecl->getNameInfo().getBeginLoc(); + 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 (!SM.isPointWithin(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 = functionDecl->getNameInfo().getBeginLoc(); + 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() + && SM.isPointWithin(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() + && !SM.isPointWithin(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) + && cast<CXXConstructorDecl>(functionDecl)->getExplicitSpecifier().isExplicit()) + || (isa<CXXConversionDecl>(functionDecl) + && cast<CXXConversionDecl>(functionDecl) + ->getExplicitSpecifier() + .isExplicit()))) + { + 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: */ |