summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/salcall.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compilerplugins/clang/salcall.cxx615
1 files changed, 615 insertions, 0 deletions
diff --git a/compilerplugins/clang/salcall.cxx b/compilerplugins/clang/salcall.cxx
new file mode 100644
index 0000000000..d2b4fc2bfb
--- /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: */