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::ParenExprClass
:
99 case Stmt::CXXNullPtrLiteralExprClass
:
102 // Conservatively assume non-identical for expressions that don't happen for us in practice
103 // when compiling the LO code base (and for which the above set of supported classes would
104 // need to be extended):
107 auto i1
= stmt1
->child_begin();
108 auto e1
= stmt1
->child_end();
109 auto i2
= stmt2
->child_begin();
110 auto e2
= stmt2
->child_end();
111 for (; i1
!= e1
; ++i1
, ++i2
) {
112 if (i2
== e2
|| !structurallyIdentical(*i1
, *i2
)) {
121 Plugin::Plugin( const InstantiationData
& data
)
122 : compiler( data
.compiler
), handler( data
.handler
), name( data
.name
)
126 DiagnosticBuilder
Plugin::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
) const
128 return handler
.report( level
, name
, message
, compiler
, loc
);
131 bool Plugin::suppressWarningAt(SourceLocation location
) const {
132 auto const start
= compiler
.getSourceManager().getSpellingLoc(location
);
133 auto const startInfo
= compiler
.getSourceManager().getDecomposedLoc(start
);
134 auto invalid
= false;
135 auto const buf
= compiler
.getSourceManager().getBufferData(startInfo
.first
, &invalid
);
138 report(DiagnosticsEngine::Fatal
, "failed to getBufferData", start
);
142 auto const label
= std::string("[-loplugin:").append(name
).append("]");
143 // Look back to the beginning of the previous line:
145 auto locInfo
= startInfo
;
147 enum class State
{ Normal
, Slash
, Comment
};
148 auto state
= State::Normal
;
150 for (auto prev
= cur
;;) {
151 auto prevInfo
= compiler
.getSourceManager().getDecomposedLoc(prev
);
152 if (prev
== compiler
.getSourceManager().getLocForStartOfFile(prevInfo
.first
)) {
153 if (state
== State::Comment
&& isDebugMode()) {
155 DiagnosticsEngine::Fatal
,
156 "beginning of file while looking for beginning of comment", prev
);
161 if (Lexer::getRawToken(
162 Lexer::GetBeginningOfToken(
163 prev
.getLocWithOffset(-1), compiler
.getSourceManager(), compiler
.getLangOpts()),
164 tok
, compiler
.getSourceManager(), compiler
.getLangOpts(), true))
168 DiagnosticsEngine::Fatal
, "failed to getRawToken",
169 prev
.getLocWithOffset(-1));
173 if (tok
.getLocation() == cur
) {
174 // Keep walking back, character by character, through whitespace preceding the current
175 // token, which Clang treats as nominally belonging to that token (so the above
176 // Lexer::getRawToken/Lexer::GetBeginningOfToken will have produced just the same tok
178 prev
= prev
.getLocWithOffset(-1);
181 cur
= tok
.getLocation();
183 if (state
== State::Comment
) {
184 // Lexer::GetBeginningOfToken (at least towards Clang 15, still) only re-scans from the
185 // start of the current line, so if we saw the end of a multi-line /*...*/ comment, we
186 // saw that as individual '/' and '*' faux-tokens, at which point we must (hopefully?)
187 // actually be at the end of such a multi-line comment, so we keep walking back to the
188 // first '/*' we encounter (TODO: which still need not be the real start of the comment,
189 // if the comment contains embedded '/*', but we could determine that only if we
190 // re-scanned from the start of the file):
191 if (!tok
.is(tok::comment
)) {
194 SmallVector
<char, 256> tmp
;
195 bool invalid
= false;
196 auto const spell
= Lexer::getSpelling(
197 prev
, tmp
, compiler
.getSourceManager(), compiler
.getLangOpts(), &invalid
);
200 report(DiagnosticsEngine::Fatal
, "failed to getSpelling", prev
);
202 } else if (!spell
.startswith("/*")) {
206 prevInfo
= compiler
.getSourceManager().getDecomposedLoc(prev
);
207 auto const end
= prev
.getLocWithOffset(tok
.getLength());
208 auto const endInfo
= compiler
.getSourceManager().getDecomposedLoc(end
);
209 assert(prevInfo
.first
== endInfo
.first
);
210 assert(prevInfo
.second
<= endInfo
.second
);
211 assert(endInfo
.first
== locInfo
.first
);
212 // Whitespace between tokens is found at the end of prev, from end to loc (unless this is a
213 // multi-line comment, in which case the whitespace has already been inspected as the
214 // whitespace following the comment's final '/' faux-token):
216 if (state
!= State::Comment
) {
217 assert(endInfo
.second
<= locInfo
.second
);
218 ws
= buf
.substr(endInfo
.second
, locInfo
.second
- endInfo
.second
);
220 for (std::size_t i
= 0;;) {
221 auto const j
= ws
.find('\n', i
);
222 if (j
== StringRef::npos
) {
234 auto str
= buf
.substr(prevInfo
.second
, endInfo
.second
- prevInfo
.second
);
235 if (tok
.is(tok::comment
) && str
.contains(label
)) {
238 for (std::size_t i
= 0;;) {
239 auto const j
= str
.find('\n', i
);
240 if (j
== StringRef::npos
) {
256 if (tok
.is(tok::slash
)) {
257 state
= State::Slash
;
261 state
= tok
.is(tok::star
) && ws
.empty() ? State::Comment
: State::Normal
;
262 //TODO: check for "ws is only folding whitespace" rather than for `ws.empty()` (but
263 // then, we must not count newlines in that whitespace twice, first as part of the
264 // whitespace following the comment's semi-final '*' faux-token and then as part of
265 // the comment token's content)
268 state
= State::Normal
;
271 // Look forward to the end of the current line:
276 if (Lexer::getRawToken(loc
, tok
, compiler
.getSourceManager(), compiler
.getLangOpts(), true))
279 report(DiagnosticsEngine::Fatal
, "failed to getRawToken", loc
);
283 // Whitespace between tokens is found at the beginning, from loc to beg:
284 auto const beg
= tok
.getLocation();
285 auto const begInfo
= compiler
.getSourceManager().getDecomposedLoc(beg
);
286 assert(begInfo
.first
== locInfo
.first
);
287 assert(begInfo
.second
>= locInfo
.second
);
288 if (buf
.substr(locInfo
.second
, begInfo
.second
- locInfo
.second
).contains('\n')) {
291 auto const next
= beg
.getLocWithOffset(tok
.getLength());
292 auto const nextInfo
= compiler
.getSourceManager().getDecomposedLoc(next
);
293 assert(nextInfo
.first
== begInfo
.first
);
294 assert(nextInfo
.second
>= begInfo
.second
);
295 auto const str
= buf
.substr(begInfo
.second
, nextInfo
.second
- begInfo
.second
);
296 if (tok
.is(tok::comment
) && str
.contains(label
)) {
299 if (tok
.is(tok::eof
) || str
.contains('\n')) {
308 void normalizeDotDotInFilePath( std::string
& s
)
310 for (std::string::size_type i
= 0;;)
313 if (i
== std::string::npos
) {
316 if (i
+ 2 == s
.length() || s
[i
+ 2] == '/') {
317 s
.erase(i
, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
318 } else if (s
[i
+ 2] == '.'
319 && (i
+ 3 == s
.length() || s
[i
+ 3] == '/'))
321 if (i
== 0) { // /..[/CCC] -> /..[/CCC]
324 auto j
= s
.rfind('/', i
- 1);
325 if (j
== std::string::npos
)
327 // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC ->
328 // CCC, to avoid wrong ../../CCC -> CCC; relative paths
329 // shouldn't happen anyway, and even if they did, wouldn't
330 // match against WORKDIR anyway, as WORKDIR should be
334 s
.erase(j
, i
+ 3 - j
); // AAA/BBB/..[/CCC] -> AAA[/CCC]
342 void Plugin::registerPlugin( Plugin
* (*create
)( const InstantiationData
& ), const char* optionName
,
343 bool isPPCallback
, bool isSharedPlugin
, bool byDefault
)
345 PluginHandler::registerPlugin( create
, optionName
, isPPCallback
, isSharedPlugin
, byDefault
);
348 bool Plugin::evaluate(const Expr
* expr
, APSInt
& x
)
350 if (compat::EvaluateAsInt(expr
, x
, compiler
.getASTContext()))
354 if (isa
<CXXNullPtrLiteralExpr
>(expr
)) {
361 const Stmt
* Plugin::getParentStmt( const Stmt
* stmt
)
363 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
364 if ( parentsRange
.begin() == parentsRange
.end())
366 return parentsRange
.begin()->get
<Stmt
>();
369 Stmt
* Plugin::getParentStmt( Stmt
* stmt
)
371 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
372 if ( parentsRange
.begin() == parentsRange
.end())
374 return const_cast<Stmt
*>(parentsRange
.begin()->get
<Stmt
>());
377 const Decl
* getFunctionDeclContext(ASTContext
& context
, const Stmt
* stmt
)
379 auto const parents
= context
.getParents(*stmt
);
380 auto it
= parents
.begin();
382 if (it
== parents
.end())
385 const Decl
*decl
= it
->get
<Decl
>();
388 if (isa
<VarDecl
>(decl
))
389 return dyn_cast
<FunctionDecl
>(decl
->getDeclContext());
393 stmt
= it
->get
<Stmt
>();
395 return getFunctionDeclContext(context
, stmt
);
400 const FunctionDecl
* Plugin::getParentFunctionDecl( const Stmt
* stmt
)
402 const Decl
*decl
= getFunctionDeclContext(compiler
.getASTContext(), stmt
);
404 return static_cast<const FunctionDecl
*>(decl
->getNonClosureContext());
409 StringRef
Plugin::getFilenameOfLocation(SourceLocation spellingLocation
) const
411 // prevent crashes when running the global-analysis plugins
412 if (!spellingLocation
.isValid())
415 static enum { NOINIT
, STDIN
, GOOD
} s_Mode(NOINIT
);
418 return compiler
.getSourceManager().getFilename(spellingLocation
);
420 else if (s_Mode
== STDIN
421 || !compiler
.getSourceManager().isInMainFile(spellingLocation
))
423 const char* bufferName
= compiler
.getSourceManager().getPresumedLoc(spellingLocation
).getFilename();
428 char const*const pCXX
= getenv("CXX");
429 if (pCXX
&& strstr(pCXX
, "sccache"))
430 { // heuristic; sccache passes file with -frewrite-directives by name
432 return getFilenameOfLocation(spellingLocation
);
434 auto const fn(compiler
.getSourceManager().getFilename(spellingLocation
));
435 if (!fn
.data()) // wtf? happens in sot/source/sdstor/stg.cxx
440 assert(fn
.startswith("/") || fn
== "<stdin>");
442 s_Mode
= fn
== "<stdin>" ? STDIN
: GOOD
;
443 return getFilenameOfLocation(spellingLocation
);
447 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation
) const
449 StringRef name
{ getFilenameOfLocation(spellingLocation
) };
450 return compiler
.getSourceManager().isInMainFile(spellingLocation
)
451 ? (isSamePathname(name
, SRCDIR
"/cppu/source/cppu/compat.cxx")
452 || isSamePathname(name
, SRCDIR
"/cppuhelper/source/compat.cxx")
453 || isSamePathname(name
, SRCDIR
"/sal/osl/all/compat.cxx"))
454 : (hasPathnamePrefix(name
, SRCDIR
"/include/com/")
455 || hasPathnamePrefix(name
, SRCDIR
"/include/cppu/")
456 || hasPathnamePrefix(name
, SRCDIR
"/include/cppuhelper/")
457 || hasPathnamePrefix(name
, SRCDIR
"/include/osl/")
458 || hasPathnamePrefix(name
, SRCDIR
"/include/rtl/")
459 || hasPathnamePrefix(name
, SRCDIR
"/include/sal/")
460 || hasPathnamePrefix(name
, SRCDIR
"/include/salhelper/")
461 || hasPathnamePrefix(name
, SRCDIR
"/include/typelib/")
462 || hasPathnamePrefix(name
, SRCDIR
"/include/uno/")
463 || hasPathnamePrefix(name
, SDKDIR
"/include/"));
466 bool Plugin::isInUnoIncludeFile(const FunctionDecl
* functionDecl
) const
468 return isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(
469 functionDecl
->getCanonicalDecl()->getNameInfo().getLoc()));
472 SourceLocation
Plugin::locationAfterToken( SourceLocation location
)
474 return Lexer::getLocForEndOfToken( location
, 0, compiler
.getSourceManager(), compiler
.getLangOpts());
477 bool Plugin::isUnitTestMode()
479 return PluginHandler::isUnitTestMode();
482 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range
)
484 // Preprocessing directives (other than _Pragma, which is not relevant here) cannot appear in
485 // macro expansions, so it is safe to just consider the range of expansion locations:
486 auto const begin
= compiler
.getSourceManager().getExpansionLoc(
488 auto const end
= compiler
.getSourceManager().getExpansionLoc(
490 assert(begin
.isFileID() && end
.isFileID());
492 || compiler
.getSourceManager().isBeforeInTranslationUnit(
497 DiagnosticsEngine::Fatal
,
498 ("unexpected broken range for Plugin::containsPreprocessingConditionalInclusion,"
503 // Conservatively assume "yes" if lexing fails:
507 for (auto loc
= begin
;;) {
509 if (Lexer::getRawToken(
510 loc
, tok
, compiler
.getSourceManager(),
511 compiler
.getLangOpts(), true))
515 DiagnosticsEngine::Fatal
,
516 ("unexpected broken range for"
517 " Plugin::containsPreprocessingConditionalInclusion, case 2"),
521 // Conservatively assume "yes" if lexing fails:
524 if (hash
&& tok
.is(tok::raw_identifier
)) {
525 auto const id
= tok
.getRawIdentifier();
526 if (id
== "if" || id
== "ifdef" || id
== "ifndef"
527 || id
== "elif" || id
== "else" || id
== "endif")
535 hash
= tok
.is(tok::hash
) && tok
.isAtStartOfLine();
536 loc
= loc
.getLocWithOffset(
538 Lexer::MeasureTokenLength(
539 loc
, compiler
.getSourceManager(),
540 compiler
.getLangOpts()),
546 bool Plugin::containsComment(SourceRange range
)
548 SourceManager
& SM
= compiler
.getSourceManager();
549 SourceLocation startLoc
= range
.getBegin();
550 SourceLocation endLoc
= range
.getEnd();
551 char const* p1
= SM
.getCharacterData(startLoc
);
552 char const* p2
= SM
.getCharacterData(endLoc
);
553 p2
+= Lexer::MeasureTokenLength(endLoc
, SM
, compiler
.getLangOpts());
555 // when doing 'make solenv.check' we don't want the special comments in the
556 // unit test files to trigger this check
557 constexpr char const comment0
[] = "// expected-error";
558 if (std::search(p1
, p2
, comment0
, comment0
+ strlen(comment0
)) != p2
)
561 // check for comments
562 constexpr char const comment1
[] = "/*";
563 constexpr char const comment2
[] = "//";
564 if (std::search(p1
, p2
, comment1
, comment1
+ strlen(comment1
)) != p2
)
566 if (std::search(p1
, p2
, comment2
, comment2
+ strlen(comment2
)) != p2
)
572 Plugin::IdenticalDefaultArgumentsResult
Plugin::checkIdenticalDefaultArguments(
573 Expr
const * argument1
, Expr
const * argument2
)
575 if ((argument1
== nullptr) != (argument2
== nullptr)) {
576 return IdenticalDefaultArgumentsResult::No
;
578 if (argument1
== nullptr) {
579 return IdenticalDefaultArgumentsResult::Yes
;
581 if (argument1
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
)
582 && argument2
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
))
584 return IdenticalDefaultArgumentsResult::Yes
;
587 if (evaluate(argument1
, x1
) && evaluate(argument2
, x2
))
590 ? IdenticalDefaultArgumentsResult::Yes
591 : IdenticalDefaultArgumentsResult::No
;
593 APFloat
f1(0.0f
), f2(0.0f
);
594 if (argument1
->EvaluateAsFloat(f1
, compiler
.getASTContext())
595 && argument2
->EvaluateAsFloat(f2
, compiler
.getASTContext()))
597 return f1
.bitwiseIsEqual(f2
)
598 ? IdenticalDefaultArgumentsResult::Yes
599 : IdenticalDefaultArgumentsResult::No
;
601 auto const desugared1
= argument1
->IgnoreParenImpCasts();
602 auto const desugared2
= argument2
->IgnoreParenImpCasts();
603 if (auto const lit1
= dyn_cast
<clang::StringLiteral
>(desugared1
)) {
604 if (auto const lit2
= dyn_cast
<clang::StringLiteral
>(desugared2
)) {
605 return lit1
->getBytes() == lit2
->getBytes()
606 ? IdenticalDefaultArgumentsResult::Yes
607 : IdenticalDefaultArgumentsResult::No
;
610 // catch params with defaults like "= OUString()"
611 for (Expr
const * e1
= desugared1
, * e2
= desugared2
;;) {
612 auto const ce1
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e1
));
613 auto const ce2
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e2
));
614 if (ce1
== nullptr || ce2
== nullptr) {
617 if (ce1
->getConstructor()->getCanonicalDecl() != ce2
->getConstructor()->getCanonicalDecl())
619 return IdenticalDefaultArgumentsResult::No
;
621 if (ce1
->isElidable() && ce2
->isElidable() && ce1
->getNumArgs() == 1
622 && ce2
->getNumArgs() == 1)
624 assert(ce1
->getConstructor()->isCopyOrMoveConstructor());
625 e1
= ce1
->getArg(0)->IgnoreImpCasts();
626 e2
= ce2
->getArg(0)->IgnoreImpCasts();
629 if (ce1
->getNumArgs() == 0 && ce2
->getNumArgs() == 0) {
630 return IdenticalDefaultArgumentsResult::Yes
;
634 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
635 // function template specializations that happen to not have been instantiated in this TU, try a
636 // structural comparison of the arguments:
637 if (structurallyIdentical(argument1
, argument2
)) {
638 return IdenticalDefaultArgumentsResult::Yes
;
642 DiagnosticsEngine::Fatal
, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
643 argument1
->getExprLoc())
644 << argument1
->getSourceRange();
646 DiagnosticsEngine::Note
, "TODO: second argument is here", argument2
->getExprLoc())
647 << argument2
->getSourceRange();
651 return IdenticalDefaultArgumentsResult::Maybe
;
654 RewritePlugin::RewritePlugin( const InstantiationData
& data
)
656 , rewriter( data
.rewriter
)
660 bool RewritePlugin::insertText( SourceLocation Loc
, StringRef Str
, bool InsertAfter
, bool indentNewLines
)
663 if (wouldRewriteWorkdir(Loc
))
665 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
666 if( !handler
.checkOverlap( Range
) )
668 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
671 if( rewriter
->InsertText( Loc
, Str
, InsertAfter
, indentNewLines
))
672 return reportEditFailure( Loc
);
673 handler
.addSourceModification(Range
);
677 bool RewritePlugin::insertTextAfter( SourceLocation Loc
, StringRef Str
)
680 if (wouldRewriteWorkdir(Loc
))
682 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
683 if( !handler
.checkOverlap( Range
) )
685 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
688 if( rewriter
->InsertTextAfter( Loc
, Str
))
689 return reportEditFailure( Loc
);
690 handler
.addSourceModification(Range
);
694 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc
, StringRef Str
)
697 if (wouldRewriteWorkdir(Loc
))
699 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
700 if( !handler
.checkOverlap( Range
) )
702 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
705 if( rewriter
->InsertTextAfterToken( Loc
, Str
))
706 return reportEditFailure( Loc
);
707 handler
.addSourceModification(Range
);
711 bool RewritePlugin::insertTextBefore( SourceLocation Loc
, StringRef Str
)
714 if (wouldRewriteWorkdir(Loc
))
716 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
717 if( !handler
.checkOverlap( Range
) )
719 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
722 if( rewriter
->InsertTextBefore( Loc
, Str
))
723 return reportEditFailure( Loc
);
724 handler
.addSourceModification(Range
);
728 bool RewritePlugin::removeText( SourceLocation Start
, unsigned Length
, RewriteOptions opts
)
730 CharSourceRange
range( SourceRange( Start
, Start
.getLocWithOffset( Length
)), false );
731 return removeText( range
, opts
);
734 bool RewritePlugin::removeText( SourceRange range
, RewriteOptions opts
)
736 return removeText( CharSourceRange( range
, true ), opts
);
739 bool RewritePlugin::removeText( CharSourceRange range
, RewriteOptions opts
)
742 if (wouldRewriteWorkdir(range
.getBegin()))
744 if( rewriter
->getRangeSize( range
, opts
) == -1 )
745 return reportEditFailure( range
.getBegin());
746 if( !handler
.checkOverlap( range
.getAsRange() ) )
748 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", range
.getBegin());
751 if( opts
.flags
& RemoveWholeStatement
|| opts
.flags
& RemoveAllWhitespace
)
753 if( !adjustRangeForOptions( &range
, opts
))
754 return reportEditFailure( range
.getBegin());
756 if( rewriter
->RemoveText( range
, opts
))
757 return reportEditFailure( range
.getBegin());
758 handler
.addSourceModification(range
.getAsRange());
762 bool RewritePlugin::adjustRangeForOptions( CharSourceRange
* range
, RewriteOptions opts
)
765 SourceManager
& SM
= rewriter
->getSourceMgr();
766 SourceLocation fileStartLoc
= SM
.getLocForStartOfFile( SM
.getFileID( range
->getBegin()));
767 if( fileStartLoc
.isInvalid())
769 bool isInvalid
= false;
770 const char* fileBuf
= SM
.getCharacterData( fileStartLoc
, &isInvalid
);
773 const char* startBuf
= SM
.getCharacterData( range
->getBegin(), &isInvalid
);
776 SourceLocation locationEnd
= range
->getEnd();
777 if( range
->isTokenRange())
778 locationEnd
= locationAfterToken( locationEnd
);
779 const char* endBuf
= SM
.getCharacterData( locationEnd
, &isInvalid
);
782 const char* startPos
= startBuf
;
784 while( startPos
>= fileBuf
&& ( *startPos
== ' ' || *startPos
== '\t' ))
786 if( startPos
>= fileBuf
&& *startPos
== '\n' )
787 startPos
= startBuf
- 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
788 const char* endPos
= endBuf
;
789 while( *endPos
== ' ' || *endPos
== '\t' )
791 if( opts
.flags
& RemoveWholeStatement
)
798 *range
= CharSourceRange( SourceRange( range
->getBegin().getLocWithOffset( startPos
- startBuf
+ 1 ),
799 locationEnd
.getLocWithOffset( endPos
- endBuf
)), false );
803 bool RewritePlugin::replaceText( SourceLocation Start
, unsigned OrigLength
, StringRef NewStr
)
806 if (wouldRewriteWorkdir(Start
))
808 SourceRange
Range(Start
, Start
.getLocWithOffset(std::max
<size_t>(OrigLength
, NewStr
.size())));
809 if( OrigLength
!= 0 && !handler
.checkOverlap( Range
) )
811 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", Start
);
814 if( rewriter
->ReplaceText( Start
, OrigLength
, NewStr
))
815 return reportEditFailure( Start
);
816 handler
.addSourceModification(Range
);
820 bool RewritePlugin::replaceText( SourceRange range
, StringRef NewStr
)
823 if (wouldRewriteWorkdir(range
.getBegin()))
825 if( rewriter
->getRangeSize( range
) == -1 )
826 return reportEditFailure( range
.getBegin());
827 if( !handler
.checkOverlap( range
) )
829 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
832 if( rewriter
->ReplaceText( range
, NewStr
))
833 return reportEditFailure( range
.getBegin());
834 handler
.addSourceModification(range
);
838 bool RewritePlugin::replaceText( SourceRange range
, SourceRange replacementRange
)
841 if (wouldRewriteWorkdir(range
.getBegin()))
843 if( rewriter
->getRangeSize( range
) == -1 )
844 return reportEditFailure( range
.getBegin());
845 if( !handler
.checkOverlap( range
) )
847 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
850 if( rewriter
->ReplaceText( range
, replacementRange
))
851 return reportEditFailure( range
.getBegin());
852 handler
.addSourceModification(range
);
856 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc
)
858 if (loc
.isInvalid() || loc
.isMacroID()) {
862 getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(loc
))
863 .startswith(WORKDIR
"/");
866 bool RewritePlugin::reportEditFailure( SourceLocation loc
)
868 report( DiagnosticsEngine::Warning
, "cannot perform source modification (macro expansion involved?)", loc
);
874 template<typename Fn
> bool checkPathname(
875 StringRef pathname
, StringRef against
, Fn check
)
877 if (check(pathname
, against
)) {
881 for (std::size_t n
= 0;;)
883 std::size_t n1
= pathname
.find('\\', n
);
884 std::size_t n2
= against
.find('\\', n
);
886 if (n1
>= against
.size()) {
887 return check(pathname
.substr(n
), against
.substr(n
));
889 if ((against
[n1
] != '/' && against
[n1
] != '\\')
890 || pathname
.substr(n
, n1
- n
) != against
.substr(n
, n1
- n
))
896 if (n2
>= pathname
.size()) {
897 return check(pathname
.substr(n
), against
.substr(n
));
899 if (pathname
[n2
] != '/'
900 || pathname
.substr(n
, n2
- n
) != against
.substr(n
, n2
- n
))
913 bool hasPathnamePrefix(StringRef pathname
, StringRef prefix
)
915 return checkPathname(
917 [](StringRef p
, StringRef a
) { return p
.startswith(a
); });
920 bool isSamePathname(StringRef pathname
, StringRef other
)
922 return checkPathname(
923 pathname
, other
, [](StringRef p
, StringRef a
) { return p
== a
; });
926 bool isSameUnoIncludePathname(StringRef fullPathname
, StringRef includePathname
)
928 llvm::SmallVector
<char, 256> buf
;
929 if (isSamePathname(fullPathname
, (SRCDIR
"/include/" + includePathname
).toStringRef(buf
))) {
933 return isSamePathname(fullPathname
, (SDKDIR
"/include/" + includePathname
).toStringRef(buf
));
936 bool hasCLanguageLinkageType(FunctionDecl
const * decl
) {
937 assert(decl
!= nullptr);
938 if (decl
->isExternC()) {
941 if (decl
->isInExternCContext()) {
947 static const CXXRecordDecl
* stripTypeSugar(QualType qt
)
949 const clang::Type
* t
= qt
.getTypePtr();
952 if (auto elaboratedType
= dyn_cast
<ElaboratedType
>(t
))
953 t
= elaboratedType
->desugar().getTypePtr();
954 else if (auto tsType
= dyn_cast
<TemplateSpecializationType
>(t
))
955 t
= tsType
->desugar().getTypePtr();
956 else if (auto sttpType
= dyn_cast
<SubstTemplateTypeParmType
>(t
))
957 t
= sttpType
->desugar().getTypePtr();
958 else if (auto tdType
= dyn_cast
<TypedefType
>(t
))
959 t
= tdType
->desugar().getTypePtr();
963 auto recordType
= dyn_cast
<RecordType
>(t
);
966 return dyn_cast_or_null
<CXXRecordDecl
>(recordType
->getDecl());
969 int derivedFromCount(const CXXRecordDecl
* subclassRecordDecl
, const CXXRecordDecl
* baseclassRecordDecl
)
971 if (!subclassRecordDecl
|| !baseclassRecordDecl
)
973 int derivedCount
= 0;
974 if (subclassRecordDecl
== baseclassRecordDecl
)
976 if (!subclassRecordDecl
->hasDefinition())
978 for (auto it
= subclassRecordDecl
->bases_begin(); it
!= subclassRecordDecl
->bases_end(); ++it
)
980 derivedCount
+= derivedFromCount(stripTypeSugar(it
->getType()), baseclassRecordDecl
);
981 // short-circuit, we only care about 0,1,2
982 if (derivedCount
> 1)
985 for (auto it
= subclassRecordDecl
->vbases_begin(); it
!= subclassRecordDecl
->vbases_end(); ++it
)
987 derivedCount
+= derivedFromCount(stripTypeSugar(it
->getType()), baseclassRecordDecl
);
988 // short-circuit, we only care about 0,1,2
989 if (derivedCount
> 1)
995 int derivedFromCount(QualType subclassQt
, QualType baseclassQt
)
997 auto baseclassRecordDecl
= stripTypeSugar(baseclassQt
);
998 if (!baseclassRecordDecl
)
1000 auto subclassRecordDecl
= stripTypeSugar(subclassQt
);
1001 if (!subclassRecordDecl
)
1004 return derivedFromCount(subclassRecordDecl
, baseclassRecordDecl
);
1007 // It looks like Clang wrongly implements DR 4
1008 // (<http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#4>) and treats
1009 // a variable declared in an 'extern "..." {...}'-style linkage-specification as
1010 // if it contained the 'extern' specifier:
1011 bool hasExternalLinkage(VarDecl
const * decl
) {
1012 if (decl
->getLinkageAndVisibility().getLinkage() != ExternalLinkage
) {
1015 for (auto ctx
= decl
->getLexicalDeclContext();
1016 ctx
->getDeclKind() != Decl::TranslationUnit
;
1017 ctx
= ctx
->getLexicalParent())
1019 if (auto ls
= dyn_cast
<LinkageSpecDecl
>(ctx
)) {
1020 if (!ls
->hasBraces()) {
1023 if (auto prev
= decl
->getPreviousDecl()) {
1024 return hasExternalLinkage(prev
);
1026 return !decl
->isInAnonymousNamespace();
1032 bool isSmartPointerType(QualType qt
)
1034 // First check whether the object type as written is, or is derived from, std::unique_ptr or
1035 // std::shared_ptr, in case the get member function is declared at a base class of that std
1037 if (loplugin::isDerivedFrom(
1038 qt
->getAsCXXRecordDecl(),
1039 [](Decl
const * decl
) {
1040 auto const dc
= loplugin::DeclCheck(decl
);
1041 return dc
.ClassOrStruct("unique_ptr").StdNamespace()
1042 || dc
.ClassOrStruct("shared_ptr").StdNamespace();
1046 // Then check the object type coerced to the type of the get member function, in
1047 // case the type-as-written is derived from one of these types (tools::SvRef is
1048 // final, but the rest are not):
1049 auto const tc2
= loplugin::TypeCheck(qt
);
1050 if (tc2
.ClassOrStruct("unique_ptr").StdNamespace()
1051 || tc2
.ClassOrStruct("shared_ptr").StdNamespace()
1052 || tc2
.Class("Reference").Namespace("uno").Namespace("star")
1053 .Namespace("sun").Namespace("com").GlobalNamespace()
1054 || tc2
.Class("Reference").Namespace("rtl").GlobalNamespace()
1055 || tc2
.Class("SvRef").Namespace("tools").GlobalNamespace()
1056 || tc2
.Class("WeakReference").Namespace("tools").GlobalNamespace()
1057 || tc2
.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace()
1058 || tc2
.Class("ScopedVclPtrInstance").GlobalNamespace()
1059 || tc2
.Class("VclPtr").GlobalNamespace()
1060 || tc2
.Class("ScopedVclPtr").GlobalNamespace()
1061 || tc2
.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
1068 bool isSmartPointerType(const Expr
* e
)
1070 // First check whether the object type as written is, or is derived from, std::unique_ptr or
1071 // std::shared_ptr, in case the get member function is declared at a base class of that std
1073 if (loplugin::isDerivedFrom(
1074 e
->IgnoreImpCasts()->getType()->getAsCXXRecordDecl(),
1075 [](Decl
const * decl
) {
1076 auto const dc
= loplugin::DeclCheck(decl
);
1077 return dc
.ClassOrStruct("unique_ptr").StdNamespace()
1078 || dc
.ClassOrStruct("shared_ptr").StdNamespace();
1082 // Then check the object type coerced to the type of the get member function, in
1083 // case the type-as-written is derived from one of these types (tools::SvRef is
1084 // final, but the rest are not):
1085 auto const tc2
= loplugin::TypeCheck(e
->getType());
1086 if (tc2
.ClassOrStruct("unique_ptr").StdNamespace()
1087 || tc2
.ClassOrStruct("shared_ptr").StdNamespace()
1088 || tc2
.Class("Reference").Namespace("uno").Namespace("star")
1089 .Namespace("sun").Namespace("com").GlobalNamespace()
1090 || tc2
.Class("Reference").Namespace("rtl").GlobalNamespace()
1091 || tc2
.Class("SvRef").Namespace("tools").GlobalNamespace()
1092 || tc2
.Class("WeakReference").Namespace("tools").GlobalNamespace()
1093 || tc2
.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace()
1094 || tc2
.Class("ScopedVclPtrInstance").GlobalNamespace()
1095 || tc2
.Class("VclPtr").GlobalNamespace()
1096 || tc2
.Class("ScopedVclPtr").GlobalNamespace()
1097 || tc2
.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
1107 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */