1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 */
9
10 #include "plugin.hxx"
11 #include "check.hxx"
12 #include "compat.hxx"
13 #include "functionaddress.hxx"
14
15 #include <algorithm>
16 #include <cassert>
17 #include <set>
18 #include <utility>
19 #include <vector>
20
21 // The SAL_CALL function annotation is only necessary on our outward
22 // facing C++ ABI, anywhere else it is just cargo-cult.
23 //
24
25 //TODO: To find inconsistencies like
26 //
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();
30 //
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.)
41
42 namespace
43 {
44 //static bool startswith(const std::string& rStr, const char* pSubStr)
45 //{
46 // return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
47 //}
48
getTemplateInstantiationPattern(CXXMethodDecl const * decl)49 CXXMethodDecl const* getTemplateInstantiationPattern(CXXMethodDecl const* decl)
50 {
51 auto const p = decl->getTemplateInstantiationPattern();
52 return p == nullptr ? decl : cast<CXXMethodDecl>(p);
53 }
54
55 class SalCall final : public loplugin::FunctionAddress<loplugin::FilteringRewritePlugin<SalCall>>
56 {
57 public:
SalCall(loplugin::InstantiationData const & data)58 explicit SalCall(loplugin::InstantiationData const& data)
59 : FunctionAddress(data)
60 {
61 }
62
run()63 virtual void run() override
64 {
65 if (TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
66 {
67 auto const& addressOfSet = getFunctionsWithAddressTaken();
68 for (auto const decl : m_decls)
69 {
70 if (addressOfSet.find(decl->getCanonicalDecl()) == addressOfSet.end())
71 {
72 handleFunctionDecl(decl);
73 }
74 }
75 }
76 }
77
78 bool VisitFunctionDecl(FunctionDecl const*);
79
80 private:
81 void handleFunctionDecl(FunctionDecl const* decl);
82 bool rewrite(SourceLocation);
83 bool isSalCallFunction(FunctionDecl const* functionDecl, SourceLocation* pLoc = nullptr);
84
85 std::set<FunctionDecl const*> m_decls;
86 };
87
VisitFunctionDecl(FunctionDecl const * decl)88 bool SalCall::VisitFunctionDecl(FunctionDecl const* decl)
89 {
90 if (ignoreLocation(decl))
91 return true;
92
93 // ignore template stuff
94 if (decl->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate)
95 return true;
96 auto recordDecl = dyn_cast<CXXRecordDecl>(decl->getDeclContext());
97 if (recordDecl
98 && (recordDecl->getTemplateSpecializationKind() != TSK_Undeclared
99 || recordDecl->isDependentContext()))
100 {
101 return true;
102 }
103
104 auto canonicalDecl = decl->getCanonicalDecl();
105
106 // ignore UNO implementations
107 if (isInUnoIncludeFile(
108 compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocation())))
109 return true;
110
111 SourceLocation rewriteLoc;
112 SourceLocation rewriteCanonicalLoc;
113 bool bDeclIsSalCall = isSalCallFunction(decl, &rewriteLoc);
114 bool bCanonicalDeclIsSalCall = isSalCallFunction(canonicalDecl, &rewriteCanonicalLoc);
115
116 // first, check for consistency, so we don't trip ourselves up on Linux, where we normally run the plugin
117 if (canonicalDecl != decl)
118 {
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)
122 {
123 // not fine
124 report(DiagnosticsEngine::Warning, "SAL_CALL inconsistency", decl->getLocation())
125 << decl->getSourceRange();
126 report(DiagnosticsEngine::Note, "SAL_CALL inconsistency", canonicalDecl->getLocation())
127 << canonicalDecl->getSourceRange();
128 return true;
129 }
130 }
131 auto methodDecl = dyn_cast<CXXMethodDecl>(canonicalDecl);
132 if (methodDecl)
133 {
134 for (auto iter = methodDecl->begin_overridden_methods();
135 iter != methodDecl->end_overridden_methods(); ++iter)
136 {
137 const CXXMethodDecl* overriddenMethod
138 = getTemplateInstantiationPattern(*iter)->getCanonicalDecl();
139 if (bCanonicalDeclIsSalCall != isSalCallFunction(overriddenMethod))
140 {
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();
147 return true;
148 }
149 }
150 }
151
152 if (!bCanonicalDeclIsSalCall)
153 return true;
154
155 if (!decl->isThisDeclarationADefinition() && !(methodDecl && methodDecl->isPure()))
156 return true;
157
158 m_decls.insert(decl);
159 return true;
160 }
161
handleFunctionDecl(FunctionDecl const * decl)162 void SalCall::handleFunctionDecl(FunctionDecl const* decl)
163 {
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()))
166 {
167 auto dc = loplugin::DeclCheck(recordDecl);
168 if (dc.Class("OProxyAggregation").Namespace("comphelper").GlobalNamespace()
169 || dc.Class("OComponentProxyAggregationHelper")
170 .Namespace("comphelper")
171 .GlobalNamespace()
172 || dc.Class("SvxShapeMaster").GlobalNamespace()
173 || dc.Class("ListBoxAccessibleBase").Namespace("accessibility").GlobalNamespace()
174 || dc.Class("AsyncEventNotifierBase").Namespace("comphelper").GlobalNamespace()
175 || dc.Class("ODescriptor")
176 .Namespace("sdbcx")
177 .Namespace("connectivity")
178 .GlobalNamespace()
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")
187 .Namespace("core")
188 .Namespace("oox")
189 .GlobalNamespace()
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()
195
196 )
197 return;
198 }
199
200 auto canonicalDecl = decl->getCanonicalDecl();
201
202 // if any of the overridden methods are SAL_CALL, we should be too
203 if (auto methodDecl = dyn_cast<CXXMethodDecl>(canonicalDecl))
204 {
205 for (auto iter = methodDecl->begin_overridden_methods();
206 iter != methodDecl->end_overridden_methods(); ++iter)
207 {
208 const CXXMethodDecl* overriddenMethod
209 = getTemplateInstantiationPattern(*iter)->getCanonicalDecl();
210 if (isSalCallFunction(overriddenMethod))
211 return;
212 }
213 }
214
215 SourceLocation rewriteLoc;
216 SourceLocation rewriteCanonicalLoc;
217 bool bDeclIsSalCall = isSalCallFunction(decl, &rewriteLoc);
218 isSalCallFunction(canonicalDecl, &rewriteCanonicalLoc);
219
220 bool bOK = rewrite(rewriteLoc);
221 if (bOK && canonicalDecl != decl)
222 {
223 bOK = rewrite(rewriteCanonicalLoc);
224 }
225 if (bOK)
226 return;
227
228 if (bDeclIsSalCall)
229 {
230 report(DiagnosticsEngine::Warning, "SAL_CALL unnecessary here",
231 rewriteLoc.isValid() ? rewriteLoc : decl->getLocation())
232 << decl->getSourceRange();
233 }
234 if (canonicalDecl != decl)
235 {
236 report(DiagnosticsEngine::Warning, "SAL_CALL unnecessary here", rewriteCanonicalLoc)
237 << canonicalDecl->getSourceRange();
238 if (!bDeclIsSalCall)
239 {
240 report(DiagnosticsEngine::Note, "defined here (without SAL_CALL decoration)",
241 decl->getLocation())
242 << decl->getSourceRange();
243 }
244 }
245 }
246
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:
isSalCallFunction(FunctionDecl const * functionDecl,SourceLocation * pLoc)249 bool SalCall::isSalCallFunction(FunctionDecl const* functionDecl, SourceLocation* pLoc)
250 {
251 assert(!functionDecl->isTemplateInstantiation());
252
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
261 //
262 // CXXMethodDecl <forms/source/xforms/datatypes.hxx:217:9, col:54>
263 // forms/source/xforms/datatyperepository.cxx:242:37 registerProperties 'void (void)' virtual
264 //
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)
269 {
270 return false;
271 }
272
273 SourceManager& SM = compiler.getSourceManager();
274 std::vector<SourceRange> ranges;
275
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)
283 {
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();
289 if (TSI == nullptr)
290 {
291 if (isDebugMode())
292 {
293 report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #1, needs investigation",
294 functionDecl->getLocation())
295 << functionDecl->getSourceRange();
296 }
297 return false;
298 }
299 auto TL = TSI->getTypeLoc().IgnoreParens();
300 if (auto ATL = TL.getAs<AttributedTypeLoc>())
301 {
302 TL = ATL.getModifiedLoc();
303 }
304 auto const FTL = TL.getAs<FunctionTypeLoc>();
305 if (!FTL)
306 {
307 // Happens when a function declaration uses a typedef for the function type, as in
308 //
309 // SAL_JNI_EXPORT javaunohelper::detail::Func_bootstrap
310 // Java_com_sun_star_comp_helper_Bootstrap_cppuhelper_1bootstrap;
311 //
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)
315 return false;
316 }
317 startLoc = FTL.getReturnLoc().getEndLoc();
318 while (SM.isMacroArgExpansion(startLoc, &startLoc))
319 {
320 }
321
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 = compat::getBeginLoc(functionDecl->getNameInfo());
327 while (SM.isMacroArgExpansion(endLoc, &endLoc))
328 {
329 }
330 while (endLoc.isMacroID() && SM.isAtStartOfImmediateMacroExpansion(endLoc, &endLoc))
331 {
332 }
333 endLoc = SM.getSpellingLoc(endLoc);
334
335 auto const slEnd = Lexer::getLocForEndOfToken(startLoc, 0, SM, compiler.getLangOpts());
336 if (slEnd.isValid())
337 {
338 // startLoc is either non-macro, or at end of macro; one source range from startLoc to
339 // endLoc:
340 startLoc = slEnd;
341 while (startLoc.isMacroID() && SM.isAtEndOfImmediateMacroExpansion(startLoc, &startLoc))
342 {
343 }
344 startLoc = SM.getSpellingLoc(startLoc);
345
346 if (startLoc.isValid() && endLoc.isValid() && startLoc != endLoc
347 && !SM.isBeforeInTranslationUnit(startLoc, endLoc))
348 {
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
351 //
352 // void (*f())();
353 //
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
357 //
358 // void SAL_CALL (*f())();
359 //
360 // that are hopefully rare):
361 startAfterReturnType = false;
362 }
363 }
364 else
365 {
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 (name.startswith("\\\n"))
375 {
376 name = name.drop_front(2);
377 while (!name.empty()
378 && (name.front() == ' ' || name.front() == '\t' || name.front() == '\n'
379 || name.front() == '\v' || name.front() == '\f'))
380 {
381 name = name.drop_front(1);
382 }
383 }
384 auto const MI = compiler.getPreprocessor()
385 .getMacroDefinitionAtLoc(&compiler.getASTContext().Idents.get(name),
386 SM.getSpellingLoc(startLoc))
387 .getMacroInfo();
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 (!compat::isPointWithin(SM, endLoc, startLoc, endLoc1))
395 {
396 ranges.emplace_back(startLoc, endLoc1);
397 startLoc = Lexer::getLocForEndOfToken(SM.getSpellingLoc(startLoc2), 0, SM,
398 compiler.getLangOpts());
399 }
400 }
401 }
402 if (!startAfterReturnType)
403 {
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 = compat::getBeginLoc(functionDecl->getNameInfo());
408 while (endLoc.isMacroID() && SM.isAtStartOfImmediateMacroExpansion(endLoc, &endLoc))
409 {
410 }
411
412 SourceRange macroRange;
413 if (SM.isMacroBodyExpansion(endLoc))
414 {
415 auto name = Lexer::getImmediateMacroName(endLoc, SM, compiler.getLangOpts());
416 while (name.startswith("\\\n"))
417 {
418 name = name.drop_front(2);
419 while (!name.empty()
420 && (name.front() == ' ' || name.front() == '\t' || name.front() == '\n'
421 || name.front() == '\v' || name.front() == '\f'))
422 {
423 name = name.drop_front(1);
424 }
425 }
426 auto const MI = compiler.getPreprocessor()
427 .getMacroDefinitionAtLoc(&compiler.getASTContext().Idents.get(name),
428 SM.getSpellingLoc(endLoc))
429 .getMacroInfo();
430 assert(MI != nullptr);
431 macroRange = SourceRange(MI->getDefinitionLoc(), MI->getDefinitionEndLoc());
432 if (isDebugMode() && macroRange.isInvalid())
433 {
434 report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #4, needs investigation",
435 functionDecl->getLocation())
436 << functionDecl->getSourceRange();
437 }
438 }
439
440 #if defined _WIN32
441 auto const macroExpansion = SM.getExpansionLoc(endLoc);
442 #endif
443 endLoc = SM.getSpellingLoc(endLoc);
444
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 && compat::isPointWithin(SM, SM.getSpellingLoc(startLoc), macroRange.getBegin(),
451 macroRange.getEnd()))
452 && SM.isAtStartOfImmediateMacroExpansion(startLoc, &startLoc))
453 {
454 }
455 #if !defined _WIN32
456 auto const macroStartLoc = startLoc;
457 #endif
458 startLoc = SM.getSpellingLoc(startLoc);
459
460 #if defined _WIN32
461 if (macroRange.isValid()
462 && !compat::isPointWithin(SM, startLoc, macroRange.getBegin(), macroRange.getEnd()))
463 {
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();
469 }
470 #else
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
474 // "explicit"):
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"
477 if (noReturnType
478 && !(functionDecl->isVirtualAsWritten()
479 || (isa<CXXConstructorDecl>(functionDecl)
480 && compat::isExplicitSpecified(cast<CXXConstructorDecl>(functionDecl)))
481 || (isa<CXXConversionDecl>(functionDecl)
482 && compat::isExplicitSpecified(cast<CXXConversionDecl>(functionDecl)))))
483 {
484 SourceLocation endLoc1;
485 if (macroStartLoc.isMacroID()
486 && SM.isAtStartOfImmediateMacroExpansion(macroStartLoc, &endLoc1))
487 {
488 // startLoc is at the start of a macro body; two source ranges, first one is looking
489 // backwards one token from the call site of the macro:
490 auto startLoc1 = endLoc1;
491 for (;;)
492 {
493 startLoc1 = Lexer::GetBeginningOfToken(startLoc1.getLocWithOffset(-1), SM,
494 compiler.getLangOpts());
495 auto const s = StringRef(
496 SM.getCharacterData(startLoc1),
497 Lexer::MeasureTokenLength(startLoc1, SM, compiler.getLangOpts()));
498 // When looking backward at least through a function-like macro replacement like
499 //
500 // | foo\ |
501 // | barbaz##X |
502 //
503 // starting at "barbaz" in the second line, the next token reported will start at "\"
504 // in the first line and include the intervening spaces and (part of? looks like an
505 // error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
506 // when looking backwards here, without even trying to look at their content:
507 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
508 || s.startswith("\\\n")))
509 {
510 break;
511 }
512 }
513 ranges.emplace_back(startLoc1, endLoc1);
514 }
515 else
516 {
517 for (;;)
518 {
519 startLoc = Lexer::GetBeginningOfToken(startLoc.getLocWithOffset(-1), SM,
520 compiler.getLangOpts());
521 auto const s = StringRef(
522 SM.getCharacterData(startLoc),
523 Lexer::MeasureTokenLength(startLoc, SM, compiler.getLangOpts()));
524 // When looking backward at least through a function-like macro replacement like
525 //
526 // | foo\ |
527 // | barbaz##X |
528 //
529 // starting at "barbaz" in the second line, the next token reported will start at "\"
530 // in the first line and include the intervening spaces and (part of? looks like an
531 // error in Clang) "barbaz", so just skip any tokens starting with backslash-newline
532 // when looking backwards here, without even trying to look at their content:
533 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
534 || s.startswith("\\\n")))
535 {
536 break;
537 }
538 }
539 }
540 }
541 #endif
542 }
543 ranges.emplace_back(startLoc, endLoc);
544
545 for (auto const& range : ranges)
546 {
547 if (range.isInvalid())
548 {
549 if (isDebugMode())
550 {
551 report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #2, needs investigation",
552 functionDecl->getLocation())
553 << functionDecl->getSourceRange();
554 }
555 return false;
556 }
557 if (isDebugMode() && range.getBegin() != range.getEnd()
558 && !SM.isBeforeInTranslationUnit(range.getBegin(), range.getEnd()))
559 {
560 report(DiagnosticsEngine::Fatal, "TODO: unexpected failure #3, needs investigation",
561 functionDecl->getLocation())
562 << functionDecl->getSourceRange();
563 }
564
565 for (auto loc = range.getBegin(); SM.isBeforeInTranslationUnit(loc, range.getEnd());)
566 {
567 unsigned n = Lexer::MeasureTokenLength(loc, SM, compiler.getLangOpts());
568 auto s = StringRef(compiler.getSourceManager().getCharacterData(loc), n);
569 while (s.startswith("\\\n"))
570 {
571 s = s.drop_front(2);
572 while (!s.empty()
573 && (s.front() == ' ' || s.front() == '\t' || s.front() == '\n'
574 || s.front() == '\v' || s.front() == '\f'))
575 {
576 s = s.drop_front(1);
577 }
578 }
579 if (s == "SAL_CALL")
580 {
581 if (pLoc)
582 *pLoc = loc;
583 return true;
584 }
585 loc = loc.getLocWithOffset(std::max<unsigned>(n, 1));
586 }
587 }
588 return false;
589 }
590
rewrite(SourceLocation locBegin)591 bool SalCall::rewrite(SourceLocation locBegin)
592 {
593 if (!rewriter)
594 return false;
595 if (!locBegin.isValid())
596 return false;
597
598 auto locEnd = locBegin.getLocWithOffset(8);
599 if (!locEnd.isValid())
600 return false;
601
602 SourceRange range(locBegin, locEnd);
603
604 if (!replaceText(locBegin, 9, ""))
605 return false;
606
607 return true;
608 }
609
610 static loplugin::Plugin::Registration<SalCall> reg("salcall", true);
611 }
612
613 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
614