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/Basic/FileManager.h>
19 #include <clang/Lex/Lexer.h>
21 #include "pluginhandler.hxx"
24 Base classes for plugin actions.
31 Expr
const * skipImplicit(Expr
const * expr
) {
32 if (auto const e
= dyn_cast
<MaterializeTemporaryExpr
>(expr
)) {
33 expr
= e
->GetTemporaryExpr()->IgnoreImpCasts();
35 if (auto const e
= dyn_cast
<CXXBindTemporaryExpr
>(expr
)) {
36 expr
= e
->getSubExpr();
41 bool structurallyIdentical(Stmt
const * stmt1
, Stmt
const * stmt2
) {
42 if (stmt1
->getStmtClass() != stmt2
->getStmtClass()) {
45 switch (stmt1
->getStmtClass()) {
46 case Stmt::CXXConstructExprClass
:
47 if (cast
<CXXConstructExpr
>(stmt1
)->getConstructor()->getCanonicalDecl()
48 != cast
<CXXConstructExpr
>(stmt2
)->getConstructor()->getCanonicalDecl())
53 case Stmt::DeclRefExprClass
:
54 if (cast
<DeclRefExpr
>(stmt1
)->getDecl()->getCanonicalDecl()
55 != cast
<DeclRefExpr
>(stmt2
)->getDecl()->getCanonicalDecl())
60 case Stmt::ImplicitCastExprClass
:
62 auto const e1
= cast
<ImplicitCastExpr
>(stmt1
);
63 auto const e2
= cast
<ImplicitCastExpr
>(stmt2
);
64 if (e1
->getCastKind() != e2
->getCastKind()
65 || e1
->getType().getCanonicalType() != e2
->getType().getCanonicalType())
71 case Stmt::MemberExprClass
:
73 auto const e1
= cast
<MemberExpr
>(stmt1
);
74 auto const e2
= cast
<MemberExpr
>(stmt2
);
75 if (e1
->isArrow() != e2
->isArrow()
76 || e1
->getType().getCanonicalType() != e2
->getType().getCanonicalType())
82 case Stmt::CXXMemberCallExprClass
:
83 case Stmt::CXXOperatorCallExprClass
:
84 if (cast
<Expr
>(stmt1
)->getType().getCanonicalType()
85 != cast
<Expr
>(stmt2
)->getType().getCanonicalType())
90 case Stmt::MaterializeTemporaryExprClass
:
91 case Stmt::ParenExprClass
:
94 // Conservatively assume non-identical for expressions that don't happen for us in practice
95 // when compiling the LO code base (and for which the above set of supported classes would
96 // need to be extended):
99 auto i1
= stmt1
->child_begin();
100 auto e1
= stmt1
->child_end();
101 auto i2
= stmt2
->child_begin();
102 auto e2
= stmt2
->child_end();
103 for (; i1
!= e1
; ++i1
, ++i2
) {
104 if (i2
== e2
|| !structurallyIdentical(*i1
, *i2
)) {
113 Plugin::Plugin( const InstantiationData
& data
)
114 : compiler( data
.compiler
), handler( data
.handler
), name( data
.name
)
118 DiagnosticBuilder
Plugin::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
) const
120 return handler
.report( level
, name
, message
, compiler
, loc
);
123 void normalizeDotDotInFilePath( std::string
& s
)
125 for (std::string::size_type i
= 0;;)
128 if (i
== std::string::npos
) {
131 if (i
+ 2 == s
.length() || s
[i
+ 2] == '/') {
132 s
.erase(i
, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
133 } else if (s
[i
+ 2] == '.'
134 && (i
+ 3 == s
.length() || s
[i
+ 3] == '/'))
136 if (i
== 0) { // /..[/CCC] -> /..[/CCC]
139 auto j
= s
.rfind('/', i
- 1);
140 if (j
== std::string::npos
)
142 // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC ->
143 // CCC, to avoid wrong ../../CCC -> CCC; relative paths
144 // shouldn't happen anyway, and even if they did, wouldn't
145 // match against WORKDIR anyway, as WORKDIR should be
149 s
.erase(j
, i
+ 3 - j
); // AAA/BBB/..[/CCC] -> AAA[/CCC]
157 void Plugin::registerPlugin( Plugin
* (*create
)( const InstantiationData
& ), const char* optionName
,
158 bool isPPCallback
, bool isSharedPlugin
, bool byDefault
)
160 PluginHandler::registerPlugin( create
, optionName
, isPPCallback
, isSharedPlugin
, byDefault
);
163 bool Plugin::evaluate(const Expr
* expr
, APSInt
& x
)
165 if (compat::EvaluateAsInt(expr
, x
, compiler
.getASTContext()))
169 if (isa
<CXXNullPtrLiteralExpr
>(expr
)) {
176 const Stmt
* Plugin::getParentStmt( const Stmt
* stmt
)
178 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
179 if ( parentsRange
.begin() == parentsRange
.end())
181 return parentsRange
.begin()->get
<Stmt
>();
184 Stmt
* Plugin::getParentStmt( Stmt
* stmt
)
186 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
187 if ( parentsRange
.begin() == parentsRange
.end())
189 return const_cast<Stmt
*>(parentsRange
.begin()->get
<Stmt
>());
192 static const Decl
* getDeclContext(ASTContext
& context
, const Stmt
* stmt
)
194 auto it
= context
.getParents(*stmt
).begin();
196 if (it
== context
.getParents(*stmt
).end())
199 const Decl
*aDecl
= it
->get
<Decl
>();
203 const Stmt
*aStmt
= it
->get
<Stmt
>();
205 return getDeclContext(context
, aStmt
);
210 const FunctionDecl
* Plugin::getParentFunctionDecl( const Stmt
* stmt
)
212 const Decl
*decl
= getDeclContext(compiler
.getASTContext(), stmt
);
214 return static_cast<const FunctionDecl
*>(decl
->getNonClosureContext());
219 StringRef
Plugin::getFileNameOfSpellingLoc(SourceLocation spellingLocation
) const
221 // prevent crashes when running the global-analysis plugins
222 if (!spellingLocation
.isValid())
225 static enum { NOINIT
, STDIN
, GOOD
} s_Mode(NOINIT
);
228 return compiler
.getSourceManager().getFilename(spellingLocation
);
230 else if (s_Mode
== STDIN
231 || !compiler
.getSourceManager().isInMainFile(spellingLocation
))
233 const char* bufferName
= compiler
.getSourceManager().getPresumedLoc(spellingLocation
).getFilename();
238 auto const fn(compiler
.getSourceManager().getFilename(spellingLocation
));
239 if (!fn
.data()) // wtf? happens in sot/source/sdstor/stg.cxx
244 assert(fn
.startswith("/") || fn
== "<stdin>");
246 s_Mode
= fn
== "<stdin>" ? STDIN
: GOOD
;
247 return getFileNameOfSpellingLoc(spellingLocation
);
251 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation
) const
253 StringRef name
{ getFileNameOfSpellingLoc(spellingLocation
) };
254 return compiler
.getSourceManager().isInMainFile(spellingLocation
)
255 ? (isSamePathname(name
, SRCDIR
"/cppu/source/cppu/compat.cxx")
256 || isSamePathname(name
, SRCDIR
"/cppuhelper/source/compat.cxx")
257 || isSamePathname(name
, SRCDIR
"/sal/osl/all/compat.cxx"))
258 : (hasPathnamePrefix(name
, SRCDIR
"/include/com/")
259 || hasPathnamePrefix(name
, SRCDIR
"/include/cppu/")
260 || hasPathnamePrefix(name
, SRCDIR
"/include/cppuhelper/")
261 || hasPathnamePrefix(name
, SRCDIR
"/include/osl/")
262 || hasPathnamePrefix(name
, SRCDIR
"/include/rtl/")
263 || hasPathnamePrefix(name
, SRCDIR
"/include/sal/")
264 || hasPathnamePrefix(name
, SRCDIR
"/include/salhelper/")
265 || hasPathnamePrefix(name
, SRCDIR
"/include/systools/")
266 || hasPathnamePrefix(name
, SRCDIR
"/include/typelib/")
267 || hasPathnamePrefix(name
, SRCDIR
"/include/uno/"));
270 bool Plugin::isInUnoIncludeFile(const FunctionDecl
* functionDecl
) const
272 return isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(
273 functionDecl
->getCanonicalDecl()->getNameInfo().getLoc()));
276 SourceLocation
Plugin::locationAfterToken( SourceLocation location
)
278 return Lexer::getLocForEndOfToken( location
, 0, compiler
.getSourceManager(), compiler
.getLangOpts());
281 bool Plugin::isUnitTestMode()
283 return PluginHandler::isUnitTestMode();
286 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range
)
288 auto const begin
= compiler
.getSourceManager().getExpansionLoc(
290 auto const end
= compiler
.getSourceManager().getExpansionLoc(
292 assert(begin
.isFileID() && end
.isFileID());
294 || compiler
.getSourceManager().isBeforeInTranslationUnit(
297 // Conservatively assume "yes" if lexing fails (e.g., due to
302 for (auto loc
= begin
;;) {
304 if (Lexer::getRawToken(
305 loc
, tok
, compiler
.getSourceManager(),
306 compiler
.getLangOpts(), true))
308 // Conservatively assume "yes" if lexing fails (e.g., due to
312 if (hash
&& tok
.is(tok::raw_identifier
)) {
313 auto const id
= tok
.getRawIdentifier();
314 if (id
== "if" || id
== "ifdef" || id
== "ifndef"
315 || id
== "elif" || id
== "else" || id
== "endif")
320 if (loc
== range
.getEnd()) {
323 hash
= tok
.is(tok::hash
) && tok
.isAtStartOfLine();
324 loc
= loc
.getLocWithOffset(
326 Lexer::MeasureTokenLength(
327 loc
, compiler
.getSourceManager(),
328 compiler
.getLangOpts()),
334 Plugin::IdenticalDefaultArgumentsResult
Plugin::checkIdenticalDefaultArguments(
335 Expr
const * argument1
, Expr
const * argument2
)
337 if ((argument1
== nullptr) != (argument2
== nullptr)) {
338 return IdenticalDefaultArgumentsResult::No
;
340 if (argument1
== nullptr) {
341 return IdenticalDefaultArgumentsResult::Yes
;
343 if (argument1
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
)
344 && argument2
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
))
346 return IdenticalDefaultArgumentsResult::Yes
;
349 if (evaluate(argument1
, x1
) && evaluate(argument2
, x2
))
352 ? IdenticalDefaultArgumentsResult::Yes
353 : IdenticalDefaultArgumentsResult::No
;
355 APFloat
f1(0.0f
), f2(0.0f
);
356 if (argument1
->EvaluateAsFloat(f1
, compiler
.getASTContext())
357 && argument2
->EvaluateAsFloat(f2
, compiler
.getASTContext()))
359 return f1
.bitwiseIsEqual(f2
)
360 ? IdenticalDefaultArgumentsResult::Yes
361 : IdenticalDefaultArgumentsResult::No
;
363 auto const desugared1
= argument1
->IgnoreParenImpCasts();
364 auto const desugared2
= argument2
->IgnoreParenImpCasts();
365 if (auto const lit1
= dyn_cast
<clang::StringLiteral
>(desugared1
)) {
366 if (auto const lit2
= dyn_cast
<clang::StringLiteral
>(desugared2
)) {
367 return lit1
->getBytes() == lit2
->getBytes()
368 ? IdenticalDefaultArgumentsResult::Yes
369 : IdenticalDefaultArgumentsResult::No
;
372 // catch params with defaults like "= OUString()"
373 for (Expr
const * e1
= desugared1
, * e2
= desugared2
;;) {
374 auto const ce1
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e1
));
375 auto const ce2
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e2
));
376 if (ce1
== nullptr || ce2
== nullptr) {
379 if (ce1
->getConstructor()->getCanonicalDecl() != ce2
->getConstructor()->getCanonicalDecl())
381 return IdenticalDefaultArgumentsResult::No
;
383 if (ce1
->isElidable() && ce2
->isElidable() && ce1
->getNumArgs() == 1
384 && ce2
->getNumArgs() == 1)
386 assert(ce1
->getConstructor()->isCopyOrMoveConstructor());
387 e1
= ce1
->getArg(0)->IgnoreImpCasts();
388 e2
= ce2
->getArg(0)->IgnoreImpCasts();
391 if (ce1
->getNumArgs() == 0 && ce2
->getNumArgs() == 0) {
392 return IdenticalDefaultArgumentsResult::Yes
;
396 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
397 // function template specializations that happen to not have been instantiated in this TU, try a
398 // structural comparison of the arguments:
399 if (structurallyIdentical(argument1
, argument2
)) {
400 return IdenticalDefaultArgumentsResult::Yes
;
404 DiagnosticsEngine::Fatal
, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
405 argument1
->getExprLoc())
406 << argument1
->getSourceRange();
408 DiagnosticsEngine::Note
, "TODO: second argument is here", argument2
->getExprLoc())
409 << argument2
->getSourceRange();
413 return IdenticalDefaultArgumentsResult::Maybe
;
416 RewritePlugin::RewritePlugin( const InstantiationData
& data
)
418 , rewriter( data
.rewriter
)
422 bool RewritePlugin::insertText( SourceLocation Loc
, StringRef Str
, bool InsertAfter
, bool indentNewLines
)
425 if (wouldRewriteWorkdir(Loc
))
427 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
428 if( !handler
.checkOverlap( Range
) )
430 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
433 if( rewriter
->InsertText( Loc
, Str
, InsertAfter
, indentNewLines
))
434 return reportEditFailure( Loc
);
435 handler
.addSourceModification(Range
);
439 bool RewritePlugin::insertTextAfter( SourceLocation Loc
, StringRef Str
)
442 if (wouldRewriteWorkdir(Loc
))
444 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
445 if( !handler
.checkOverlap( Range
) )
447 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
450 if( rewriter
->InsertTextAfter( Loc
, Str
))
451 return reportEditFailure( Loc
);
452 handler
.addSourceModification(Range
);
456 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc
, StringRef Str
)
459 if (wouldRewriteWorkdir(Loc
))
461 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
462 if( !handler
.checkOverlap( Range
) )
464 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
467 if( rewriter
->InsertTextAfterToken( Loc
, Str
))
468 return reportEditFailure( Loc
);
469 handler
.addSourceModification(Range
);
473 bool RewritePlugin::insertTextBefore( SourceLocation Loc
, StringRef Str
)
476 if (wouldRewriteWorkdir(Loc
))
478 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
479 if( !handler
.checkOverlap( Range
) )
481 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
484 if( rewriter
->InsertTextBefore( Loc
, Str
))
485 return reportEditFailure( Loc
);
486 handler
.addSourceModification(Range
);
490 bool RewritePlugin::removeText( SourceLocation Start
, unsigned Length
, RewriteOptions opts
)
492 CharSourceRange
range( SourceRange( Start
, Start
.getLocWithOffset( Length
)), false );
493 return removeText( range
, opts
);
496 bool RewritePlugin::removeText( SourceRange range
, RewriteOptions opts
)
498 return removeText( CharSourceRange( range
, true ), opts
);
501 bool RewritePlugin::removeText( CharSourceRange range
, RewriteOptions opts
)
504 if (wouldRewriteWorkdir(range
.getBegin()))
506 if( rewriter
->getRangeSize( range
, opts
) == -1 )
507 return reportEditFailure( range
.getBegin());
508 if( !handler
.checkOverlap( range
.getAsRange() ) )
510 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", range
.getBegin());
513 if( opts
.flags
& RemoveWholeStatement
|| opts
.flags
& RemoveAllWhitespace
)
515 if( !adjustRangeForOptions( &range
, opts
))
516 return reportEditFailure( range
.getBegin());
518 if( rewriter
->RemoveText( range
, opts
))
519 return reportEditFailure( range
.getBegin());
520 handler
.addSourceModification(range
.getAsRange());
524 bool RewritePlugin::adjustRangeForOptions( CharSourceRange
* range
, RewriteOptions opts
)
527 SourceManager
& SM
= rewriter
->getSourceMgr();
528 SourceLocation fileStartLoc
= SM
.getLocForStartOfFile( SM
.getFileID( range
->getBegin()));
529 if( fileStartLoc
.isInvalid())
531 bool isInvalid
= false;
532 const char* fileBuf
= SM
.getCharacterData( fileStartLoc
, &isInvalid
);
535 const char* startBuf
= SM
.getCharacterData( range
->getBegin(), &isInvalid
);
538 SourceLocation locationEnd
= range
->getEnd();
539 if( range
->isTokenRange())
540 locationEnd
= locationAfterToken( locationEnd
);
541 const char* endBuf
= SM
.getCharacterData( locationEnd
, &isInvalid
);
544 const char* startPos
= startBuf
;
546 while( startPos
>= fileBuf
&& ( *startPos
== ' ' || *startPos
== '\t' ))
548 if( startPos
>= fileBuf
&& *startPos
== '\n' )
549 startPos
= startBuf
- 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
550 const char* endPos
= endBuf
;
551 while( *endPos
== ' ' || *endPos
== '\t' )
553 if( opts
.flags
& RemoveWholeStatement
)
560 *range
= CharSourceRange( SourceRange( range
->getBegin().getLocWithOffset( startPos
- startBuf
+ 1 ),
561 locationEnd
.getLocWithOffset( endPos
- endBuf
)), false );
565 bool RewritePlugin::replaceText( SourceLocation Start
, unsigned OrigLength
, StringRef NewStr
)
568 if (wouldRewriteWorkdir(Start
))
570 SourceRange
Range(Start
, Start
.getLocWithOffset(std::max
<size_t>(OrigLength
, NewStr
.size())));
571 if( OrigLength
!= 0 && !handler
.checkOverlap( Range
) )
573 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", Start
);
576 if( rewriter
->ReplaceText( Start
, OrigLength
, NewStr
))
577 return reportEditFailure( Start
);
578 handler
.addSourceModification(Range
);
582 bool RewritePlugin::replaceText( SourceRange range
, StringRef NewStr
)
585 if (wouldRewriteWorkdir(range
.getBegin()))
587 if( rewriter
->getRangeSize( range
) == -1 )
588 return reportEditFailure( range
.getBegin());
589 if( !handler
.checkOverlap( range
) )
591 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
594 if( rewriter
->ReplaceText( range
, NewStr
))
595 return reportEditFailure( range
.getBegin());
596 handler
.addSourceModification(range
);
600 bool RewritePlugin::replaceText( SourceRange range
, SourceRange replacementRange
)
603 if (wouldRewriteWorkdir(range
.getBegin()))
605 if( rewriter
->getRangeSize( range
) == -1 )
606 return reportEditFailure( range
.getBegin());
607 if( !handler
.checkOverlap( range
) )
609 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
612 if( rewriter
->ReplaceText( range
, replacementRange
))
613 return reportEditFailure( range
.getBegin());
614 handler
.addSourceModification(range
);
618 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc
)
620 if (loc
.isInvalid() || loc
.isMacroID()) {
624 compiler
.getSourceManager().getFilename(
625 compiler
.getSourceManager().getSpellingLoc(loc
))
626 .startswith(WORKDIR
"/");
629 bool RewritePlugin::reportEditFailure( SourceLocation loc
)
631 report( DiagnosticsEngine::Warning
, "cannot perform source modification (macro expansion involved?)", loc
);
637 template<typename Fn
> bool checkPathname(
638 StringRef pathname
, StringRef against
, Fn check
)
640 if (check(pathname
, against
)) {
644 for (std::size_t n
= 0;;)
646 std::size_t n1
= pathname
.find('\\', n
);
647 std::size_t n2
= against
.find('\\', n
);
649 if (n1
>= against
.size()) {
650 return check(pathname
.substr(n
), against
.substr(n
));
652 if ((against
[n1
] != '/' && against
[n1
] != '\\')
653 || pathname
.substr(n
, n1
- n
) != against
.substr(n
, n1
- n
))
659 if (n2
>= pathname
.size()) {
660 return check(pathname
.substr(n
), against
.substr(n
));
662 if (pathname
[n2
] != '/'
663 || pathname
.substr(n
, n2
- n
) != against
.substr(n
, n2
- n
))
676 bool hasPathnamePrefix(StringRef pathname
, StringRef prefix
)
678 return checkPathname(
680 [](StringRef p
, StringRef a
) { return p
.startswith(a
); });
683 bool isSamePathname(StringRef pathname
, StringRef other
)
685 return checkPathname(
686 pathname
, other
, [](StringRef p
, StringRef a
) { return p
== a
; });
691 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */