1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
18 #include <clang/AST/ParentMapContext.h>
19 #include <clang/Basic/FileManager.h>
20 #include <clang/Lex/Lexer.h>
22 #include "config_clang.h"
25 #include "pluginhandler.hxx"
29 Base classes for plugin actions.
36 Expr
const * skipImplicit(Expr
const * expr
) {
37 if (auto const e
= dyn_cast
<MaterializeTemporaryExpr
>(expr
)) {
38 expr
= e
->getSubExpr()->IgnoreImpCasts();
40 if (auto const e
= dyn_cast
<CXXBindTemporaryExpr
>(expr
)) {
41 expr
= e
->getSubExpr();
46 bool structurallyIdentical(Stmt
const * stmt1
, Stmt
const * stmt2
) {
47 if (stmt1
->getStmtClass() != stmt2
->getStmtClass()) {
50 switch (stmt1
->getStmtClass()) {
51 case Stmt::CXXConstructExprClass
:
52 if (cast
<CXXConstructExpr
>(stmt1
)->getConstructor()->getCanonicalDecl()
53 != cast
<CXXConstructExpr
>(stmt2
)->getConstructor()->getCanonicalDecl())
58 case Stmt::DeclRefExprClass
:
59 if (cast
<DeclRefExpr
>(stmt1
)->getDecl()->getCanonicalDecl()
60 != cast
<DeclRefExpr
>(stmt2
)->getDecl()->getCanonicalDecl())
65 case Stmt::ImplicitCastExprClass
:
67 auto const e1
= cast
<ImplicitCastExpr
>(stmt1
);
68 auto const e2
= cast
<ImplicitCastExpr
>(stmt2
);
69 if (e1
->getCastKind() != e2
->getCastKind()
70 || e1
->getType().getCanonicalType() != e2
->getType().getCanonicalType())
76 case Stmt::MemberExprClass
:
78 auto const e1
= cast
<MemberExpr
>(stmt1
);
79 auto const e2
= cast
<MemberExpr
>(stmt2
);
80 if (e1
->isArrow() != e2
->isArrow()
81 || e1
->getType().getCanonicalType() != e2
->getType().getCanonicalType())
87 case Stmt::CXXMemberCallExprClass
:
88 case Stmt::CXXOperatorCallExprClass
:
89 if (cast
<Expr
>(stmt1
)->getType().getCanonicalType()
90 != cast
<Expr
>(stmt2
)->getType().getCanonicalType())
95 case Stmt::MaterializeTemporaryExprClass
:
96 case Stmt::CXXBindTemporaryExprClass
:
97 case Stmt::CXXDefaultArgExprClass
:
98 case Stmt::ParenExprClass
:
100 case Stmt::CXXNullPtrLiteralExprClass
:
102 case Stmt::StringLiteralClass
:
103 return cast
<clang::StringLiteral
>(stmt1
)->getBytes()
104 == cast
<clang::StringLiteral
>(stmt2
)->getBytes();
106 // Conservatively assume non-identical for expressions that don't happen for us in practice
107 // when compiling the LO code base (and for which the above set of supported classes would
108 // need to be extended):
111 auto i1
= stmt1
->child_begin();
112 auto e1
= stmt1
->child_end();
113 auto i2
= stmt2
->child_begin();
114 auto e2
= stmt2
->child_end();
115 for (; i1
!= e1
; ++i1
, ++i2
) {
116 if (i2
== e2
|| !structurallyIdentical(*i1
, *i2
)) {
125 Plugin::Plugin( const InstantiationData
& data
)
126 : compiler( data
.compiler
), handler( data
.handler
), name( data
.name
)
130 DiagnosticBuilder
Plugin::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
) const
132 return handler
.report( level
, name
, message
, compiler
, loc
);
135 bool Plugin::suppressWarningAt(SourceLocation location
) const {
136 auto const start
= compiler
.getSourceManager().getSpellingLoc(location
);
137 auto const startInfo
= compiler
.getSourceManager().getDecomposedLoc(start
);
138 auto invalid
= false;
139 auto const buf
= compiler
.getSourceManager().getBufferData(startInfo
.first
, &invalid
);
142 report(DiagnosticsEngine::Fatal
, "failed to getBufferData", start
);
146 auto const label
= std::string("[-loplugin:").append(name
).append("]");
147 // Look back to the beginning of the previous line:
149 auto locInfo
= startInfo
;
151 enum class State
{ Normal
, Slash
, Comment
};
152 auto state
= State::Normal
;
154 for (auto prev
= cur
;;) {
155 auto prevInfo
= compiler
.getSourceManager().getDecomposedLoc(prev
);
156 if (prev
== compiler
.getSourceManager().getLocForStartOfFile(prevInfo
.first
)) {
157 if (state
== State::Comment
&& isDebugMode()) {
159 DiagnosticsEngine::Fatal
,
160 "beginning of file while looking for beginning of comment", prev
);
165 if (Lexer::getRawToken(
166 Lexer::GetBeginningOfToken(
167 prev
.getLocWithOffset(-1), compiler
.getSourceManager(), compiler
.getLangOpts()),
168 tok
, compiler
.getSourceManager(), compiler
.getLangOpts(), true))
172 DiagnosticsEngine::Fatal
, "failed to getRawToken",
173 prev
.getLocWithOffset(-1));
177 if (tok
.getLocation() == cur
) {
178 // Keep walking back, character by character, through whitespace preceding the current
179 // token, which Clang treats as nominally belonging to that token (so the above
180 // Lexer::getRawToken/Lexer::GetBeginningOfToken will have produced just the same tok
182 prev
= prev
.getLocWithOffset(-1);
185 cur
= tok
.getLocation();
187 if (state
== State::Comment
) {
188 // Lexer::GetBeginningOfToken (at least towards Clang 15, still) only re-scans from the
189 // start of the current line, so if we saw the end of a multi-line /*...*/ comment, we
190 // saw that as individual '/' and '*' faux-tokens, at which point we must (hopefully?)
191 // actually be at the end of such a multi-line comment, so we keep walking back to the
192 // first '/*' we encounter (TODO: which still need not be the real start of the comment,
193 // if the comment contains embedded '/*', but we could determine that only if we
194 // re-scanned from the start of the file):
195 if (!tok
.is(tok::comment
)) {
198 SmallVector
<char, 256> tmp
;
199 bool invalid
= false;
200 auto const spell
= Lexer::getSpelling(
201 prev
, tmp
, compiler
.getSourceManager(), compiler
.getLangOpts(), &invalid
);
204 report(DiagnosticsEngine::Fatal
, "failed to getSpelling", prev
);
206 } else if (!spell
.startswith("/*")) {
210 prevInfo
= compiler
.getSourceManager().getDecomposedLoc(prev
);
211 auto const end
= prev
.getLocWithOffset(tok
.getLength());
212 auto const endInfo
= compiler
.getSourceManager().getDecomposedLoc(end
);
213 assert(prevInfo
.first
== endInfo
.first
);
214 assert(prevInfo
.second
<= endInfo
.second
);
215 assert(endInfo
.first
== locInfo
.first
);
216 // Whitespace between tokens is found at the end of prev, from end to loc (unless this is a
217 // multi-line comment, in which case the whitespace has already been inspected as the
218 // whitespace following the comment's final '/' faux-token):
220 if (state
!= State::Comment
) {
221 assert(endInfo
.second
<= locInfo
.second
);
222 ws
= buf
.substr(endInfo
.second
, locInfo
.second
- endInfo
.second
);
224 for (std::size_t i
= 0;;) {
225 auto const j
= ws
.find('\n', i
);
226 if (j
== StringRef::npos
) {
238 auto str
= buf
.substr(prevInfo
.second
, endInfo
.second
- prevInfo
.second
);
239 if (tok
.is(tok::comment
) && str
.contains(label
)) {
242 for (std::size_t i
= 0;;) {
243 auto const j
= str
.find('\n', i
);
244 if (j
== StringRef::npos
) {
260 if (tok
.is(tok::slash
)) {
261 state
= State::Slash
;
265 state
= tok
.is(tok::star
) && ws
.empty() ? State::Comment
: State::Normal
;
266 //TODO: check for "ws is only folding whitespace" rather than for `ws.empty()` (but
267 // then, we must not count newlines in that whitespace twice, first as part of the
268 // whitespace following the comment's semi-final '*' faux-token and then as part of
269 // the comment token's content)
272 state
= State::Normal
;
275 // Look forward to the end of the current line:
280 if (Lexer::getRawToken(loc
, tok
, compiler
.getSourceManager(), compiler
.getLangOpts(), true))
283 report(DiagnosticsEngine::Fatal
, "failed to getRawToken", loc
);
287 // Whitespace between tokens is found at the beginning, from loc to beg:
288 auto const beg
= tok
.getLocation();
289 auto const begInfo
= compiler
.getSourceManager().getDecomposedLoc(beg
);
290 assert(begInfo
.first
== locInfo
.first
);
291 assert(begInfo
.second
>= locInfo
.second
);
292 if (buf
.substr(locInfo
.second
, begInfo
.second
- locInfo
.second
).contains('\n')) {
295 auto const next
= beg
.getLocWithOffset(tok
.getLength());
296 auto const nextInfo
= compiler
.getSourceManager().getDecomposedLoc(next
);
297 assert(nextInfo
.first
== begInfo
.first
);
298 assert(nextInfo
.second
>= begInfo
.second
);
299 auto const str
= buf
.substr(begInfo
.second
, nextInfo
.second
- begInfo
.second
);
300 if (tok
.is(tok::comment
) && str
.contains(label
)) {
303 if (tok
.is(tok::eof
) || str
.contains('\n')) {
312 void normalizeDotDotInFilePath( std::string
& s
)
314 for (std::string::size_type i
= 0;;)
317 if (i
== std::string::npos
) {
320 if (i
+ 2 == s
.length() || s
[i
+ 2] == '/') {
321 s
.erase(i
, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
322 } else if (s
[i
+ 2] == '.'
323 && (i
+ 3 == s
.length() || s
[i
+ 3] == '/'))
325 if (i
== 0) { // /..[/CCC] -> /..[/CCC]
328 auto j
= s
.rfind('/', i
- 1);
329 if (j
== std::string::npos
)
331 // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC ->
332 // CCC, to avoid wrong ../../CCC -> CCC; relative paths
333 // shouldn't happen anyway, and even if they did, wouldn't
334 // match against WORKDIR anyway, as WORKDIR should be
338 s
.erase(j
, i
+ 3 - j
); // AAA/BBB/..[/CCC] -> AAA[/CCC]
346 void Plugin::registerPlugin( Plugin
* (*create
)( const InstantiationData
& ), const char* optionName
,
347 bool isPPCallback
, bool isSharedPlugin
, bool byDefault
)
349 PluginHandler::registerPlugin( create
, optionName
, isPPCallback
, isSharedPlugin
, byDefault
);
352 bool Plugin::evaluate(const Expr
* expr
, APSInt
& x
)
354 if (compat::EvaluateAsInt(expr
, x
, compiler
.getASTContext()))
358 if (isa
<CXXNullPtrLiteralExpr
>(expr
)) {
365 const Stmt
* Plugin::getParentStmt( const Stmt
* stmt
)
367 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
368 if ( parentsRange
.begin() == parentsRange
.end())
370 return parentsRange
.begin()->get
<Stmt
>();
373 Stmt
* Plugin::getParentStmt( Stmt
* stmt
)
375 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
376 if ( parentsRange
.begin() == parentsRange
.end())
378 return const_cast<Stmt
*>(parentsRange
.begin()->get
<Stmt
>());
381 const Decl
* getFunctionDeclContext(ASTContext
& context
, const Stmt
* stmt
)
383 auto const parents
= context
.getParents(*stmt
);
384 auto it
= parents
.begin();
386 if (it
== parents
.end())
389 const Decl
*decl
= it
->get
<Decl
>();
392 if (isa
<VarDecl
>(decl
))
393 return dyn_cast
<FunctionDecl
>(decl
->getDeclContext());
397 stmt
= it
->get
<Stmt
>();
399 return getFunctionDeclContext(context
, stmt
);
404 const FunctionDecl
* Plugin::getParentFunctionDecl( const Stmt
* stmt
)
406 const Decl
*decl
= getFunctionDeclContext(compiler
.getASTContext(), stmt
);
408 return static_cast<const FunctionDecl
*>(decl
->getNonClosureContext());
413 StringRef
Plugin::getFilenameOfLocation(SourceLocation spellingLocation
) const
415 // prevent crashes when running the global-analysis plugins
416 if (!spellingLocation
.isValid())
419 static enum { NOINIT
, STDIN
, GOOD
} s_Mode(NOINIT
);
422 return compiler
.getSourceManager().getFilename(spellingLocation
);
424 else if (s_Mode
== STDIN
425 || !compiler
.getSourceManager().isInMainFile(spellingLocation
))
427 const char* bufferName
= compiler
.getSourceManager().getPresumedLoc(spellingLocation
).getFilename();
432 char const*const pCXX
= getenv("CXX");
433 if (pCXX
&& strstr(pCXX
, "sccache"))
434 { // heuristic; sccache passes file with -frewrite-directives by name
436 return getFilenameOfLocation(spellingLocation
);
438 auto const fn(compiler
.getSourceManager().getFilename(spellingLocation
));
439 if (!fn
.data()) // wtf? happens in sot/source/sdstor/stg.cxx
444 assert(fn
.startswith("/") || fn
== "<stdin>");
446 s_Mode
= fn
== "<stdin>" ? STDIN
: GOOD
;
447 return getFilenameOfLocation(spellingLocation
);
451 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation
) const
453 StringRef name
{ getFilenameOfLocation(spellingLocation
) };
454 return compiler
.getSourceManager().isInMainFile(spellingLocation
)
455 ? (isSamePathname(name
, SRCDIR
"/cppu/source/cppu/compat.cxx")
456 || isSamePathname(name
, SRCDIR
"/cppuhelper/source/compat.cxx")
457 || isSamePathname(name
, SRCDIR
"/sal/osl/all/compat.cxx"))
458 : (hasPathnamePrefix(name
, SRCDIR
"/include/com/")
459 || hasPathnamePrefix(name
, SRCDIR
"/include/cppu/")
460 || hasPathnamePrefix(name
, SRCDIR
"/include/cppuhelper/")
461 || hasPathnamePrefix(name
, SRCDIR
"/include/osl/")
462 || hasPathnamePrefix(name
, SRCDIR
"/include/rtl/")
463 || hasPathnamePrefix(name
, SRCDIR
"/include/sal/")
464 || hasPathnamePrefix(name
, SRCDIR
"/include/salhelper/")
465 || hasPathnamePrefix(name
, SRCDIR
"/include/typelib/")
466 || hasPathnamePrefix(name
, SRCDIR
"/include/uno/")
467 || hasPathnamePrefix(name
, SDKDIR
"/include/"));
470 bool Plugin::isInUnoIncludeFile(const FunctionDecl
* functionDecl
) const
472 return isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(
473 functionDecl
->getCanonicalDecl()->getNameInfo().getLoc()));
476 SourceLocation
Plugin::locationAfterToken( SourceLocation location
)
478 return Lexer::getLocForEndOfToken( location
, 0, compiler
.getSourceManager(), compiler
.getLangOpts());
481 bool Plugin::isUnitTestMode()
483 return PluginHandler::isUnitTestMode();
486 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range
)
488 // Preprocessing directives (other than _Pragma, which is not relevant here) cannot appear in
489 // macro expansions, so it is safe to just consider the range of expansion locations:
490 auto const begin
= compiler
.getSourceManager().getExpansionLoc(
492 auto const end
= compiler
.getSourceManager().getExpansionLoc(
494 assert(begin
.isFileID() && end
.isFileID());
496 || compiler
.getSourceManager().isBeforeInTranslationUnit(
501 DiagnosticsEngine::Fatal
,
502 ("unexpected broken range for Plugin::containsPreprocessingConditionalInclusion,"
507 // Conservatively assume "yes" if lexing fails:
511 for (auto loc
= begin
;;) {
513 if (Lexer::getRawToken(
514 loc
, tok
, compiler
.getSourceManager(),
515 compiler
.getLangOpts(), true))
519 DiagnosticsEngine::Fatal
,
520 ("unexpected broken range for"
521 " Plugin::containsPreprocessingConditionalInclusion, case 2"),
525 // Conservatively assume "yes" if lexing fails:
528 if (hash
&& tok
.is(tok::raw_identifier
)) {
529 auto const id
= tok
.getRawIdentifier();
530 if (id
== "if" || id
== "ifdef" || id
== "ifndef"
531 || id
== "elif" || id
== "else" || id
== "endif")
539 hash
= tok
.is(tok::hash
) && tok
.isAtStartOfLine();
540 loc
= loc
.getLocWithOffset(
542 Lexer::MeasureTokenLength(
543 loc
, compiler
.getSourceManager(),
544 compiler
.getLangOpts()),
550 bool Plugin::containsComment(SourceRange range
)
552 SourceManager
& SM
= compiler
.getSourceManager();
553 SourceLocation startLoc
= range
.getBegin();
554 SourceLocation endLoc
= range
.getEnd();
555 char const* p1
= SM
.getCharacterData(startLoc
);
556 char const* p2
= SM
.getCharacterData(endLoc
);
557 p2
+= Lexer::MeasureTokenLength(endLoc
, SM
, compiler
.getLangOpts());
559 // when doing 'make solenv.check' we don't want the special comments in the
560 // unit test files to trigger this check
561 constexpr char const comment0
[] = "// expected-error";
562 if (std::search(p1
, p2
, comment0
, comment0
+ strlen(comment0
)) != p2
)
565 // check for comments
566 constexpr char const comment1
[] = "/*";
567 constexpr char const comment2
[] = "//";
568 if (std::search(p1
, p2
, comment1
, comment1
+ strlen(comment1
)) != p2
)
570 if (std::search(p1
, p2
, comment2
, comment2
+ strlen(comment2
)) != p2
)
576 Plugin::IdenticalDefaultArgumentsResult
Plugin::checkIdenticalDefaultArguments(
577 Expr
const * argument1
, Expr
const * argument2
)
579 if ((argument1
== nullptr) != (argument2
== nullptr)) {
580 return IdenticalDefaultArgumentsResult::No
;
582 if (argument1
== nullptr) {
583 return IdenticalDefaultArgumentsResult::Yes
;
585 if (argument1
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
)
586 && argument2
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
))
588 return IdenticalDefaultArgumentsResult::Yes
;
591 if (evaluate(argument1
, x1
) && evaluate(argument2
, x2
))
594 ? IdenticalDefaultArgumentsResult::Yes
595 : IdenticalDefaultArgumentsResult::No
;
597 APFloat
f1(0.0f
), f2(0.0f
);
598 if (argument1
->EvaluateAsFloat(f1
, compiler
.getASTContext())
599 && argument2
->EvaluateAsFloat(f2
, compiler
.getASTContext()))
601 return f1
.bitwiseIsEqual(f2
)
602 ? IdenticalDefaultArgumentsResult::Yes
603 : IdenticalDefaultArgumentsResult::No
;
605 auto const desugared1
= argument1
->IgnoreParenImpCasts();
606 auto const desugared2
= argument2
->IgnoreParenImpCasts();
607 if (auto const lit1
= dyn_cast
<clang::StringLiteral
>(desugared1
)) {
608 if (auto const lit2
= dyn_cast
<clang::StringLiteral
>(desugared2
)) {
609 return lit1
->getBytes() == lit2
->getBytes()
610 ? IdenticalDefaultArgumentsResult::Yes
611 : IdenticalDefaultArgumentsResult::No
;
614 // catch params with defaults like "= OUString()"
615 for (Expr
const * e1
= desugared1
, * e2
= desugared2
;;) {
616 auto const ce1
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e1
));
617 auto const ce2
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e2
));
618 if (ce1
== nullptr || ce2
== nullptr) {
621 if (ce1
->getConstructor()->getCanonicalDecl() != ce2
->getConstructor()->getCanonicalDecl())
623 return IdenticalDefaultArgumentsResult::No
;
625 if (ce1
->isElidable() && ce2
->isElidable() && ce1
->getNumArgs() == 1
626 && ce2
->getNumArgs() == 1)
628 assert(ce1
->getConstructor()->isCopyOrMoveConstructor());
629 e1
= ce1
->getArg(0)->IgnoreImpCasts();
630 e2
= ce2
->getArg(0)->IgnoreImpCasts();
633 if (ce1
->getNumArgs() == 0 && ce2
->getNumArgs() == 0) {
634 return IdenticalDefaultArgumentsResult::Yes
;
638 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
639 // function template specializations that happen to not have been instantiated in this TU, try a
640 // structural comparison of the arguments:
641 if (structurallyIdentical(argument1
, argument2
)) {
642 return IdenticalDefaultArgumentsResult::Yes
;
646 DiagnosticsEngine::Fatal
, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
647 argument1
->getExprLoc())
648 << argument1
->getSourceRange();
650 DiagnosticsEngine::Note
, "TODO: second argument is here", argument2
->getExprLoc())
651 << argument2
->getSourceRange();
655 return IdenticalDefaultArgumentsResult::Maybe
;
658 RewritePlugin::RewritePlugin( const InstantiationData
& data
)
660 , rewriter( data
.rewriter
)
664 bool RewritePlugin::insertText( SourceLocation Loc
, StringRef Str
, bool InsertAfter
, bool indentNewLines
)
667 if (wouldRewriteWorkdir(Loc
))
669 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
670 if( !handler
.checkOverlap( Range
) )
672 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
675 if( rewriter
->InsertText( Loc
, Str
, InsertAfter
, indentNewLines
))
676 return reportEditFailure( Loc
);
677 handler
.addSourceModification(Range
);
681 bool RewritePlugin::insertTextAfter( SourceLocation Loc
, StringRef Str
)
684 if (wouldRewriteWorkdir(Loc
))
686 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
687 if( !handler
.checkOverlap( Range
) )
689 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
692 if( rewriter
->InsertTextAfter( Loc
, Str
))
693 return reportEditFailure( Loc
);
694 handler
.addSourceModification(Range
);
698 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc
, StringRef Str
)
701 if (wouldRewriteWorkdir(Loc
))
703 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
704 if( !handler
.checkOverlap( Range
) )
706 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
709 if( rewriter
->InsertTextAfterToken( Loc
, Str
))
710 return reportEditFailure( Loc
);
711 handler
.addSourceModification(Range
);
715 bool RewritePlugin::insertTextBefore( SourceLocation Loc
, StringRef Str
)
718 if (wouldRewriteWorkdir(Loc
))
720 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
721 if( !handler
.checkOverlap( Range
) )
723 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
726 if( rewriter
->InsertTextBefore( Loc
, Str
))
727 return reportEditFailure( Loc
);
728 handler
.addSourceModification(Range
);
732 bool RewritePlugin::removeText( SourceLocation Start
, unsigned Length
, RewriteOptions opts
)
734 CharSourceRange
range( SourceRange( Start
, Start
.getLocWithOffset( Length
)), false );
735 return removeText( range
, opts
);
738 bool RewritePlugin::removeText( SourceRange range
, RewriteOptions opts
)
740 return removeText( CharSourceRange( range
, true ), opts
);
743 bool RewritePlugin::removeText( CharSourceRange range
, RewriteOptions opts
)
746 if (wouldRewriteWorkdir(range
.getBegin()))
748 if( rewriter
->getRangeSize( range
, opts
) == -1 )
749 return reportEditFailure( range
.getBegin());
750 if( !handler
.checkOverlap( range
.getAsRange() ) )
752 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", range
.getBegin());
755 if( opts
.flags
& RemoveWholeStatement
|| opts
.flags
& RemoveAllWhitespace
)
757 if( !adjustRangeForOptions( &range
, opts
))
758 return reportEditFailure( range
.getBegin());
760 if( rewriter
->RemoveText( range
, opts
))
761 return reportEditFailure( range
.getBegin());
762 handler
.addSourceModification(range
.getAsRange());
766 bool RewritePlugin::adjustRangeForOptions( CharSourceRange
* range
, RewriteOptions opts
)
769 SourceManager
& SM
= rewriter
->getSourceMgr();
770 SourceLocation fileStartLoc
= SM
.getLocForStartOfFile( SM
.getFileID( range
->getBegin()));
771 if( fileStartLoc
.isInvalid())
773 bool isInvalid
= false;
774 const char* fileBuf
= SM
.getCharacterData( fileStartLoc
, &isInvalid
);
777 const char* startBuf
= SM
.getCharacterData( range
->getBegin(), &isInvalid
);
780 SourceLocation locationEnd
= range
->getEnd();
781 if( range
->isTokenRange())
782 locationEnd
= locationAfterToken( locationEnd
);
783 const char* endBuf
= SM
.getCharacterData( locationEnd
, &isInvalid
);
786 const char* startPos
= startBuf
;
788 while( startPos
>= fileBuf
&& ( *startPos
== ' ' || *startPos
== '\t' ))
790 if( startPos
>= fileBuf
&& *startPos
== '\n' )
791 startPos
= startBuf
- 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
792 const char* endPos
= endBuf
;
793 while( *endPos
== ' ' || *endPos
== '\t' )
795 if( opts
.flags
& RemoveWholeStatement
)
802 *range
= CharSourceRange( SourceRange( range
->getBegin().getLocWithOffset( startPos
- startBuf
+ 1 ),
803 locationEnd
.getLocWithOffset( endPos
- endBuf
)), false );
807 bool RewritePlugin::replaceText( SourceLocation Start
, unsigned OrigLength
, StringRef NewStr
)
810 if (wouldRewriteWorkdir(Start
))
812 SourceRange
Range(Start
, Start
.getLocWithOffset(std::max
<size_t>(OrigLength
, NewStr
.size())));
813 if( OrigLength
!= 0 && !handler
.checkOverlap( Range
) )
815 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", Start
);
818 if( rewriter
->ReplaceText( Start
, OrigLength
, NewStr
))
819 return reportEditFailure( Start
);
820 handler
.addSourceModification(Range
);
824 bool RewritePlugin::replaceText( SourceRange range
, StringRef NewStr
)
827 if (wouldRewriteWorkdir(range
.getBegin()))
829 if( rewriter
->getRangeSize( range
) == -1 )
830 return reportEditFailure( range
.getBegin());
831 if( !handler
.checkOverlap( range
) )
833 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
836 if( rewriter
->ReplaceText( range
, NewStr
))
837 return reportEditFailure( range
.getBegin());
838 handler
.addSourceModification(range
);
842 bool RewritePlugin::replaceText( SourceRange range
, SourceRange replacementRange
)
845 if (wouldRewriteWorkdir(range
.getBegin()))
847 if( rewriter
->getRangeSize( range
) == -1 )
848 return reportEditFailure( range
.getBegin());
849 if( !handler
.checkOverlap( range
) )
851 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
854 if( rewriter
->ReplaceText( range
, replacementRange
))
855 return reportEditFailure( range
.getBegin());
856 handler
.addSourceModification(range
);
860 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc
)
862 if (loc
.isInvalid() || loc
.isMacroID()) {
866 getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(loc
))
867 .startswith(WORKDIR
"/");
870 bool RewritePlugin::reportEditFailure( SourceLocation loc
)
872 report( DiagnosticsEngine::Warning
, "cannot perform source modification (macro expansion involved?)", loc
);
878 template<typename Fn
> bool checkPathname(
879 StringRef pathname
, StringRef against
, Fn check
)
881 if (check(pathname
, against
)) {
885 for (std::size_t n
= 0;;)
887 std::size_t n1
= pathname
.find('\\', n
);
888 std::size_t n2
= against
.find('\\', n
);
890 if (n1
>= against
.size()) {
891 return check(pathname
.substr(n
), against
.substr(n
));
893 if ((against
[n1
] != '/' && against
[n1
] != '\\')
894 || pathname
.substr(n
, n1
- n
) != against
.substr(n
, n1
- n
))
900 if (n2
>= pathname
.size()) {
901 return check(pathname
.substr(n
), against
.substr(n
));
903 if (pathname
[n2
] != '/'
904 || pathname
.substr(n
, n2
- n
) != against
.substr(n
, n2
- n
))
917 bool hasPathnamePrefix(StringRef pathname
, StringRef prefix
)
919 return checkPathname(
921 [](StringRef p
, StringRef a
) { return p
.startswith(a
); });
924 bool isSamePathname(StringRef pathname
, StringRef other
)
926 return checkPathname(
927 pathname
, other
, [](StringRef p
, StringRef a
) { return p
== a
; });
930 bool isSameUnoIncludePathname(StringRef fullPathname
, StringRef includePathname
)
932 llvm::SmallVector
<char, 256> buf
;
933 if (isSamePathname(fullPathname
, (SRCDIR
"/include/" + includePathname
).toStringRef(buf
))) {
937 return isSamePathname(fullPathname
, (SDKDIR
"/include/" + includePathname
).toStringRef(buf
));
940 bool hasCLanguageLinkageType(FunctionDecl
const * decl
) {
941 assert(decl
!= nullptr);
942 if (decl
->isExternC()) {
945 if (decl
->isInExternCContext()) {
951 static const CXXRecordDecl
* stripTypeSugar(QualType qt
)
953 const clang::Type
* t
= qt
.getTypePtr();
956 if (auto elaboratedType
= dyn_cast
<ElaboratedType
>(t
))
957 t
= elaboratedType
->desugar().getTypePtr();
958 else if (auto tsType
= dyn_cast
<TemplateSpecializationType
>(t
))
959 t
= tsType
->desugar().getTypePtr();
960 else if (auto sttpType
= dyn_cast
<SubstTemplateTypeParmType
>(t
))
961 t
= sttpType
->desugar().getTypePtr();
962 else if (auto tdType
= dyn_cast
<TypedefType
>(t
))
963 t
= tdType
->desugar().getTypePtr();
967 auto recordType
= dyn_cast
<RecordType
>(t
);
970 return dyn_cast_or_null
<CXXRecordDecl
>(recordType
->getDecl());
973 int derivedFromCount(const CXXRecordDecl
* subclassRecordDecl
, const CXXRecordDecl
* baseclassRecordDecl
)
975 if (!subclassRecordDecl
|| !baseclassRecordDecl
)
977 int derivedCount
= 0;
978 if (subclassRecordDecl
== baseclassRecordDecl
)
980 if (!subclassRecordDecl
->hasDefinition())
982 for (auto it
= subclassRecordDecl
->bases_begin(); it
!= subclassRecordDecl
->bases_end(); ++it
)
984 derivedCount
+= derivedFromCount(stripTypeSugar(it
->getType()), baseclassRecordDecl
);
985 // short-circuit, we only care about 0,1,2
986 if (derivedCount
> 1)
989 for (auto it
= subclassRecordDecl
->vbases_begin(); it
!= subclassRecordDecl
->vbases_end(); ++it
)
991 derivedCount
+= derivedFromCount(stripTypeSugar(it
->getType()), baseclassRecordDecl
);
992 // short-circuit, we only care about 0,1,2
993 if (derivedCount
> 1)
999 int derivedFromCount(QualType subclassQt
, QualType baseclassQt
)
1001 auto baseclassRecordDecl
= stripTypeSugar(baseclassQt
);
1002 if (!baseclassRecordDecl
)
1004 auto subclassRecordDecl
= stripTypeSugar(subclassQt
);
1005 if (!subclassRecordDecl
)
1008 return derivedFromCount(subclassRecordDecl
, baseclassRecordDecl
);
1011 // It looks like Clang wrongly implements DR 4
1012 // (<http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#4>) and treats
1013 // a variable declared in an 'extern "..." {...}'-style linkage-specification as
1014 // if it contained the 'extern' specifier:
1015 bool hasExternalLinkage(VarDecl
const * decl
) {
1016 if (decl
->getLinkageAndVisibility().getLinkage() != compat::Linkage::External
) {
1019 for (auto ctx
= decl
->getLexicalDeclContext();
1020 ctx
->getDeclKind() != Decl::TranslationUnit
;
1021 ctx
= ctx
->getLexicalParent())
1023 if (auto ls
= dyn_cast
<LinkageSpecDecl
>(ctx
)) {
1024 if (!ls
->hasBraces()) {
1027 if (auto prev
= decl
->getPreviousDecl()) {
1028 return hasExternalLinkage(prev
);
1030 return !decl
->isInAnonymousNamespace();
1036 bool isSmartPointerType(QualType qt
)
1038 // First check whether the object type as written is, or is derived from, std::unique_ptr or
1039 // std::shared_ptr, in case the get member function is declared at a base class of that std
1041 if (loplugin::isDerivedFrom(
1042 qt
->getAsCXXRecordDecl(),
1043 [](Decl
const * decl
) {
1044 auto const dc
= loplugin::DeclCheck(decl
);
1045 return dc
.ClassOrStruct("unique_ptr").StdNamespace()
1046 || dc
.ClassOrStruct("shared_ptr").StdNamespace();
1050 // Then check the object type coerced to the type of the get member function, in
1051 // case the type-as-written is derived from one of these types (tools::SvRef is
1052 // final, but the rest are not):
1053 auto const tc2
= loplugin::TypeCheck(qt
);
1054 if (tc2
.ClassOrStruct("unique_ptr").StdNamespace()
1055 || tc2
.ClassOrStruct("shared_ptr").StdNamespace()
1056 || tc2
.Class("Reference").Namespace("uno").Namespace("star")
1057 .Namespace("sun").Namespace("com").GlobalNamespace()
1058 || tc2
.Class("Reference").Namespace("rtl").GlobalNamespace()
1059 || tc2
.Class("SvRef").Namespace("tools").GlobalNamespace()
1060 || tc2
.Class("WeakReference").Namespace("tools").GlobalNamespace()
1061 || tc2
.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace()
1062 || tc2
.Class("ScopedVclPtrInstance").GlobalNamespace()
1063 || tc2
.Class("VclPtr").GlobalNamespace()
1064 || tc2
.Class("ScopedVclPtr").GlobalNamespace()
1065 || tc2
.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
1072 bool isSmartPointerType(const Expr
* e
)
1074 // First check whether the object type as written is, or is derived from, std::unique_ptr or
1075 // std::shared_ptr, in case the get member function is declared at a base class of that std
1077 if (loplugin::isDerivedFrom(
1078 e
->IgnoreImpCasts()->getType()->getAsCXXRecordDecl(),
1079 [](Decl
const * decl
) {
1080 auto const dc
= loplugin::DeclCheck(decl
);
1081 return dc
.ClassOrStruct("unique_ptr").StdNamespace()
1082 || dc
.ClassOrStruct("shared_ptr").StdNamespace();
1086 // Then check the object type coerced to the type of the get member function, in
1087 // case the type-as-written is derived from one of these types (tools::SvRef is
1088 // final, but the rest are not):
1089 auto const tc2
= loplugin::TypeCheck(e
->getType());
1090 if (tc2
.ClassOrStruct("unique_ptr").StdNamespace()
1091 || tc2
.ClassOrStruct("shared_ptr").StdNamespace()
1092 || tc2
.Class("Reference").Namespace("uno").Namespace("star")
1093 .Namespace("sun").Namespace("com").GlobalNamespace()
1094 || tc2
.Class("Reference").Namespace("rtl").GlobalNamespace()
1095 || tc2
.Class("SvRef").Namespace("tools").GlobalNamespace()
1096 || tc2
.Class("WeakReference").Namespace("tools").GlobalNamespace()
1097 || tc2
.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace()
1098 || tc2
.Class("ScopedVclPtrInstance").GlobalNamespace()
1099 || tc2
.Class("VclPtr").GlobalNamespace()
1100 || tc2
.Class("ScopedVclPtr").GlobalNamespace()
1101 || tc2
.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
1111 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */