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
, bool isPPCallback
, bool byDefault
)
159 PluginHandler::registerPlugin( create
, optionName
, isPPCallback
, byDefault
);
162 bool Plugin::evaluate(const Expr
* expr
, APSInt
& x
)
164 if (expr
->EvaluateAsInt(x
, compiler
.getASTContext()))
168 if (isa
<CXXNullPtrLiteralExpr
>(expr
)) {
175 const Stmt
* Plugin::getParentStmt( const Stmt
* stmt
)
177 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
178 if ( parentsRange
.begin() == parentsRange
.end())
180 return parentsRange
.begin()->get
<Stmt
>();
183 Stmt
* Plugin::getParentStmt( Stmt
* stmt
)
185 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
186 if ( parentsRange
.begin() == parentsRange
.end())
188 return const_cast<Stmt
*>(parentsRange
.begin()->get
<Stmt
>());
191 static const Decl
* getDeclContext(ASTContext
& context
, const Stmt
* stmt
)
193 auto it
= context
.getParents(*stmt
).begin();
195 if (it
== context
.getParents(*stmt
).end())
198 const Decl
*aDecl
= it
->get
<Decl
>();
202 const Stmt
*aStmt
= it
->get
<Stmt
>();
204 return getDeclContext(context
, aStmt
);
209 const FunctionDecl
* Plugin::getParentFunctionDecl( const Stmt
* stmt
)
211 const Decl
*decl
= getDeclContext(compiler
.getASTContext(), stmt
);
213 return static_cast<const FunctionDecl
*>(decl
->getNonClosureContext());
219 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation
) const
222 compiler
.getSourceManager().getFilename(spellingLocation
) };
223 return compiler
.getSourceManager().isInMainFile(spellingLocation
)
224 ? (isSamePathname(name
, SRCDIR
"/cppu/source/cppu/compat.cxx")
225 || isSamePathname(name
, SRCDIR
"/cppuhelper/source/compat.cxx")
226 || isSamePathname(name
, SRCDIR
"/sal/osl/all/compat.cxx"))
227 : (hasPathnamePrefix(name
, SRCDIR
"/include/com/")
228 || hasPathnamePrefix(name
, SRCDIR
"/include/cppu/")
229 || hasPathnamePrefix(name
, SRCDIR
"/include/cppuhelper/")
230 || hasPathnamePrefix(name
, SRCDIR
"/include/osl/")
231 || hasPathnamePrefix(name
, SRCDIR
"/include/rtl/")
232 || hasPathnamePrefix(name
, SRCDIR
"/include/sal/")
233 || hasPathnamePrefix(name
, SRCDIR
"/include/salhelper/")
234 || hasPathnamePrefix(name
, SRCDIR
"/include/systools/")
235 || hasPathnamePrefix(name
, SRCDIR
"/include/typelib/")
236 || hasPathnamePrefix(name
, SRCDIR
"/include/uno/"));
239 bool Plugin::isInUnoIncludeFile(const FunctionDecl
* functionDecl
) const
241 return isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(
242 functionDecl
->getCanonicalDecl()->getNameInfo().getLoc()));
245 SourceLocation
Plugin::locationAfterToken( SourceLocation location
)
247 return Lexer::getLocForEndOfToken( location
, 0, compiler
.getSourceManager(), compiler
.getLangOpts());
250 bool Plugin::isUnitTestMode()
252 return PluginHandler::isUnitTestMode();
255 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range
)
257 auto const begin
= compiler
.getSourceManager().getExpansionLoc(
259 auto const end
= compiler
.getSourceManager().getExpansionLoc(
261 assert(begin
.isFileID() && end
.isFileID());
263 || compiler
.getSourceManager().isBeforeInTranslationUnit(
266 // Conservatively assume "yes" if lexing fails (e.g., due to
271 for (auto loc
= begin
;;) {
273 if (Lexer::getRawToken(
274 loc
, tok
, compiler
.getSourceManager(),
275 compiler
.getLangOpts(), true))
277 // Conservatively assume "yes" if lexing fails (e.g., due to
281 if (hash
&& tok
.is(tok::raw_identifier
)) {
282 auto const id
= tok
.getRawIdentifier();
283 if (id
== "if" || id
== "ifdef" || id
== "ifndef"
284 || id
== "elif" || id
== "else" || id
== "endif")
289 if (loc
== range
.getEnd()) {
292 hash
= tok
.is(tok::hash
) && tok
.isAtStartOfLine();
293 loc
= loc
.getLocWithOffset(
295 Lexer::MeasureTokenLength(
296 loc
, compiler
.getSourceManager(),
297 compiler
.getLangOpts()),
303 Plugin::IdenticalDefaultArgumentsResult
Plugin::checkIdenticalDefaultArguments(
304 Expr
const * argument1
, Expr
const * argument2
)
306 if ((argument1
== nullptr) != (argument2
== nullptr)) {
307 return IdenticalDefaultArgumentsResult::No
;
309 if (argument1
== nullptr) {
310 return IdenticalDefaultArgumentsResult::Yes
;
312 if (argument1
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
)
313 && argument2
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
))
315 return IdenticalDefaultArgumentsResult::Yes
;
318 if (evaluate(argument1
, x1
) && evaluate(argument2
, x2
))
321 ? IdenticalDefaultArgumentsResult::Yes
322 : IdenticalDefaultArgumentsResult::No
;
324 #if CLANG_VERSION >= 30900
325 APFloat
f1(0.0f
), f2(0.0f
);
326 if (argument1
->EvaluateAsFloat(f1
, compiler
.getASTContext())
327 && argument2
->EvaluateAsFloat(f2
, compiler
.getASTContext()))
329 return f1
.bitwiseIsEqual(f2
)
330 ? IdenticalDefaultArgumentsResult::Yes
331 : IdenticalDefaultArgumentsResult::No
;
334 auto const desugared1
= argument1
->IgnoreParenImpCasts();
335 auto const desugared2
= argument2
->IgnoreParenImpCasts();
336 if (auto const lit1
= dyn_cast
<clang::StringLiteral
>(desugared1
)) {
337 if (auto const lit2
= dyn_cast
<clang::StringLiteral
>(desugared2
)) {
338 return lit1
->getBytes() == lit2
->getBytes()
339 ? IdenticalDefaultArgumentsResult::Yes
340 : IdenticalDefaultArgumentsResult::No
;
343 // catch params with defaults like "= OUString()"
344 for (Expr
const * e1
= desugared1
, * e2
= desugared2
;;) {
345 auto const ce1
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e1
));
346 auto const ce2
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e2
));
347 if (ce1
== nullptr || ce2
== nullptr) {
350 if (ce1
->getConstructor()->getCanonicalDecl() != ce2
->getConstructor()->getCanonicalDecl())
352 return IdenticalDefaultArgumentsResult::No
;
354 if (ce1
->isElidable() && ce2
->isElidable() && ce1
->getNumArgs() == 1
355 && ce2
->getNumArgs() == 1)
357 assert(ce1
->getConstructor()->isCopyOrMoveConstructor());
358 e1
= ce1
->getArg(0)->IgnoreImpCasts();
359 e2
= ce2
->getArg(0)->IgnoreImpCasts();
362 if (ce1
->getNumArgs() == 0 && ce2
->getNumArgs() == 0) {
363 return IdenticalDefaultArgumentsResult::Yes
;
367 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
368 // function template specializations that happen to not have been instantiated in this TU, try a
369 // structural comparison of the arguments:
370 if (structurallyIdentical(argument1
, argument2
)) {
371 return IdenticalDefaultArgumentsResult::Yes
;
375 DiagnosticsEngine::Fatal
, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
376 argument1
->getExprLoc())
377 << argument1
->getSourceRange();
379 DiagnosticsEngine::Note
, "TODO: second argument is here", argument2
->getExprLoc())
380 << argument2
->getSourceRange();
384 return IdenticalDefaultArgumentsResult::Maybe
;
387 RewritePlugin::RewritePlugin( const InstantiationData
& data
)
389 , rewriter( data
.rewriter
)
393 bool RewritePlugin::insertText( SourceLocation Loc
, StringRef Str
, bool InsertAfter
, bool indentNewLines
)
396 if (wouldRewriteWorkdir(Loc
))
398 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
399 if( !handler
.checkOverlap( Range
) )
401 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
404 if( rewriter
->InsertText( Loc
, Str
, InsertAfter
, indentNewLines
))
405 return reportEditFailure( Loc
);
406 handler
.addSourceModification(Range
);
410 bool RewritePlugin::insertTextAfter( SourceLocation Loc
, StringRef Str
)
413 if (wouldRewriteWorkdir(Loc
))
415 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
416 if( !handler
.checkOverlap( Range
) )
418 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
421 if( rewriter
->InsertTextAfter( Loc
, Str
))
422 return reportEditFailure( Loc
);
423 handler
.addSourceModification(Range
);
427 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc
, StringRef Str
)
430 if (wouldRewriteWorkdir(Loc
))
432 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
433 if( !handler
.checkOverlap( Range
) )
435 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
438 if( rewriter
->InsertTextAfterToken( Loc
, Str
))
439 return reportEditFailure( Loc
);
440 handler
.addSourceModification(Range
);
444 bool RewritePlugin::insertTextBefore( SourceLocation Loc
, StringRef Str
)
447 if (wouldRewriteWorkdir(Loc
))
449 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
450 if( !handler
.checkOverlap( Range
) )
452 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
455 if( rewriter
->InsertTextBefore( Loc
, Str
))
456 return reportEditFailure( Loc
);
457 handler
.addSourceModification(Range
);
461 bool RewritePlugin::removeText( SourceLocation Start
, unsigned Length
, RewriteOptions opts
)
463 CharSourceRange
range( SourceRange( Start
, Start
.getLocWithOffset( Length
)), false );
464 return removeText( range
, opts
);
467 bool RewritePlugin::removeText( SourceRange range
, RewriteOptions opts
)
469 return removeText( CharSourceRange( range
, true ), opts
);
472 bool RewritePlugin::removeText( CharSourceRange range
, RewriteOptions opts
)
475 if (wouldRewriteWorkdir(range
.getBegin()))
477 if( rewriter
->getRangeSize( range
, opts
) == -1 )
478 return reportEditFailure( range
.getBegin());
479 if( !handler
.checkOverlap( range
.getAsRange() ) )
481 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", range
.getBegin());
484 if( opts
.flags
& RemoveWholeStatement
|| opts
.flags
& RemoveAllWhitespace
)
486 if( !adjustRangeForOptions( &range
, opts
))
487 return reportEditFailure( range
.getBegin());
489 if( rewriter
->RemoveText( range
, opts
))
490 return reportEditFailure( range
.getBegin());
491 handler
.addSourceModification(range
.getAsRange());
495 bool RewritePlugin::adjustRangeForOptions( CharSourceRange
* range
, RewriteOptions opts
)
498 SourceManager
& SM
= rewriter
->getSourceMgr();
499 SourceLocation fileStartLoc
= SM
.getLocForStartOfFile( SM
.getFileID( range
->getBegin()));
500 if( fileStartLoc
.isInvalid())
502 bool isInvalid
= false;
503 const char* fileBuf
= SM
.getCharacterData( fileStartLoc
, &isInvalid
);
506 const char* startBuf
= SM
.getCharacterData( range
->getBegin(), &isInvalid
);
509 SourceLocation locationEnd
= range
->getEnd();
510 if( range
->isTokenRange())
511 locationEnd
= locationAfterToken( locationEnd
);
512 const char* endBuf
= SM
.getCharacterData( locationEnd
, &isInvalid
);
515 const char* startPos
= startBuf
;
517 while( startPos
>= fileBuf
&& ( *startPos
== ' ' || *startPos
== '\t' ))
519 if( startPos
>= fileBuf
&& *startPos
== '\n' )
520 startPos
= startBuf
- 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
521 const char* endPos
= endBuf
;
522 while( *endPos
== ' ' || *endPos
== '\t' )
524 if( opts
.flags
& RemoveWholeStatement
)
531 *range
= CharSourceRange( SourceRange( range
->getBegin().getLocWithOffset( startPos
- startBuf
+ 1 ),
532 locationEnd
.getLocWithOffset( endPos
- endBuf
)), false );
536 bool RewritePlugin::replaceText( SourceLocation Start
, unsigned OrigLength
, StringRef NewStr
)
539 if (wouldRewriteWorkdir(Start
))
541 SourceRange
Range(Start
, Start
.getLocWithOffset(std::max
<size_t>(OrigLength
, NewStr
.size())));
542 if( OrigLength
!= 0 && !handler
.checkOverlap( Range
) )
544 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", Start
);
547 if( rewriter
->ReplaceText( Start
, OrigLength
, NewStr
))
548 return reportEditFailure( Start
);
549 handler
.addSourceModification(Range
);
553 bool RewritePlugin::replaceText( SourceRange range
, StringRef NewStr
)
556 if (wouldRewriteWorkdir(range
.getBegin()))
558 if( rewriter
->getRangeSize( range
) == -1 )
559 return reportEditFailure( range
.getBegin());
560 if( !handler
.checkOverlap( range
) )
562 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
565 if( rewriter
->ReplaceText( range
, NewStr
))
566 return reportEditFailure( range
.getBegin());
567 handler
.addSourceModification(range
);
571 bool RewritePlugin::replaceText( SourceRange range
, SourceRange replacementRange
)
574 if (wouldRewriteWorkdir(range
.getBegin()))
576 if( rewriter
->getRangeSize( range
) == -1 )
577 return reportEditFailure( range
.getBegin());
578 if( !handler
.checkOverlap( range
) )
580 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
583 if( rewriter
->ReplaceText( range
, replacementRange
))
584 return reportEditFailure( range
.getBegin());
585 handler
.addSourceModification(range
);
589 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc
)
591 if (loc
.isInvalid() || loc
.isMacroID()) {
595 compiler
.getSourceManager().getFilename(
596 compiler
.getSourceManager().getSpellingLoc(loc
))
597 .startswith(WORKDIR
"/");
600 bool RewritePlugin::reportEditFailure( SourceLocation loc
)
602 report( DiagnosticsEngine::Warning
, "cannot perform source modification (macro expansion involved?)", loc
);
608 template<typename Fn
> bool checkPathname(
609 StringRef pathname
, StringRef against
, Fn check
)
611 if (check(pathname
, against
)) {
615 for (std::size_t n
= 0;;)
617 std::size_t n1
= pathname
.find('\\', n
);
618 std::size_t n2
= against
.find('\\', n
);
620 if (n1
>= against
.size()) {
621 return check(pathname
.substr(n
), against
.substr(n
));
623 if ((against
[n1
] != '/' && against
[n1
] != '\\')
624 || pathname
.substr(n
, n1
- n
) != against
.substr(n
, n1
- n
))
630 if (n2
>= pathname
.size()) {
631 return check(pathname
.substr(n
), against
.substr(n
));
633 if (pathname
[n2
] != '/'
634 || pathname
.substr(n
, n2
- n
) != against
.substr(n
, n2
- n
))
647 bool hasPathnamePrefix(StringRef pathname
, StringRef prefix
)
649 return checkPathname(
651 [](StringRef p
, StringRef a
) { return p
.startswith(a
); });
654 bool isSamePathname(StringRef pathname
, StringRef other
)
656 return checkPathname(
657 pathname
, other
, [](StringRef p
, StringRef a
) { return p
== a
; });
662 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */