android: Reuse launcher icon in activities
[LibreOffice.git] / compilerplugins / clang / plugin.cxx
blob94c5809ab7305cf0a42038270d8cf41bee4ef488
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * Based on LLVM/Clang.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
12 #include "plugin.hxx"
14 #include <cassert>
15 #include <cstddef>
16 #include <string>
18 #include <clang/AST/ParentMapContext.h>
19 #include <clang/Basic/FileManager.h>
20 #include <clang/Lex/Lexer.h>
22 #include "config_clang.h"
24 #include "compat.hxx"
25 #include "pluginhandler.hxx"
26 #include "check.hxx"
29 Base classes for plugin actions.
31 namespace loplugin
34 namespace {
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();
43 return expr;
46 bool structurallyIdentical(Stmt const * stmt1, Stmt const * stmt2) {
47 if (stmt1->getStmtClass() != stmt2->getStmtClass()) {
48 return false;
50 switch (stmt1->getStmtClass()) {
51 case Stmt::CXXConstructExprClass:
52 if (cast<CXXConstructExpr>(stmt1)->getConstructor()->getCanonicalDecl()
53 != cast<CXXConstructExpr>(stmt2)->getConstructor()->getCanonicalDecl())
55 return false;
57 break;
58 case Stmt::DeclRefExprClass:
59 if (cast<DeclRefExpr>(stmt1)->getDecl()->getCanonicalDecl()
60 != cast<DeclRefExpr>(stmt2)->getDecl()->getCanonicalDecl())
62 return false;
64 break;
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())
72 return false;
74 break;
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())
83 return false;
85 break;
87 case Stmt::CXXMemberCallExprClass:
88 case Stmt::CXXOperatorCallExprClass:
89 if (cast<Expr>(stmt1)->getType().getCanonicalType()
90 != cast<Expr>(stmt2)->getType().getCanonicalType())
92 return false;
94 break;
95 case Stmt::MaterializeTemporaryExprClass:
96 case Stmt::CXXBindTemporaryExprClass:
97 case Stmt::ParenExprClass:
98 break;
99 case Stmt::CXXNullPtrLiteralExprClass:
100 return true;
101 default:
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):
105 return false;
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)) {
113 return false;
116 return i2 == e2;
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);
136 if (invalid) {
137 if (isDebugMode()) {
138 report(DiagnosticsEngine::Fatal, "failed to getBufferData", start);
140 return false;
142 auto const label = std::string("[-loplugin:").append(name).append("]");
143 // Look back to the beginning of the previous line:
144 auto loc = start;
145 auto locInfo = startInfo;
146 auto cur = loc;
147 enum class State { Normal, Slash, Comment };
148 auto state = State::Normal;
149 auto newlines = 0;
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()) {
154 report(
155 DiagnosticsEngine::Fatal,
156 "beginning of file while looking for beginning of comment", prev);
158 break;
160 Token tok;
161 if (Lexer::getRawToken(
162 Lexer::GetBeginningOfToken(
163 prev.getLocWithOffset(-1), compiler.getSourceManager(), compiler.getLangOpts()),
164 tok, compiler.getSourceManager(), compiler.getLangOpts(), true))
166 if (isDebugMode()) {
167 report(
168 DiagnosticsEngine::Fatal, "failed to getRawToken",
169 prev.getLocWithOffset(-1));
171 break;
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
177 // again):
178 prev = prev.getLocWithOffset(-1);
179 continue;
181 cur = tok.getLocation();
182 prev = cur;
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)) {
192 continue;
194 SmallVector<char, 256> tmp;
195 bool invalid = false;
196 auto const spell = Lexer::getSpelling(
197 prev, tmp, compiler.getSourceManager(), compiler.getLangOpts(), &invalid);
198 if (invalid) {
199 if (isDebugMode()) {
200 report(DiagnosticsEngine::Fatal, "failed to getSpelling", prev);
202 } else if (!spell.startswith("/*")) {
203 continue;
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):
215 StringRef ws;
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) {
223 break;
225 ++newlines;
226 if (newlines == 2) {
227 break;
229 i = j + 1;
231 if (newlines == 2) {
232 break;
234 auto str = buf.substr(prevInfo.second, endInfo.second - prevInfo.second);
235 if (tok.is(tok::comment) && str.contains(label)) {
236 return true;
238 for (std::size_t i = 0;;) {
239 auto const j = str.find('\n', i);
240 if (j == StringRef::npos) {
241 break;
243 ++newlines;
244 if (newlines == 2) {
245 break;
247 i = j + 1;
249 if (newlines == 2) {
250 break;
252 loc = prev;
253 locInfo = prevInfo;
254 switch (state) {
255 case State::Normal:
256 if (tok.is(tok::slash)) {
257 state = State::Slash;
259 break;
260 case 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)
266 break;
267 case State::Comment:
268 state = State::Normal;
271 // Look forward to the end of the current line:
272 loc = start;
273 locInfo = startInfo;
274 for (;;) {
275 Token tok;
276 if (Lexer::getRawToken(loc, tok, compiler.getSourceManager(), compiler.getLangOpts(), true))
278 if (isDebugMode()) {
279 report(DiagnosticsEngine::Fatal, "failed to getRawToken", loc);
281 break;
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')) {
289 break;
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)) {
297 return true;
299 if (tok.is(tok::eof) || str.contains('\n')) {
300 break;
302 loc = next;
303 locInfo = nextInfo;
305 return false;
308 void normalizeDotDotInFilePath( std::string & s )
310 for (std::string::size_type i = 0;;)
312 i = s.find("/.", i);
313 if (i == std::string::npos) {
314 break;
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]
322 break;
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
331 // absolute):
332 break;
334 s.erase(j, i + 3 - j); // AAA/BBB/..[/CCC] -> AAA[/CCC]
335 i = j;
336 } else {
337 i += 2;
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()))
352 return true;
354 if (isa<CXXNullPtrLiteralExpr>(expr)) {
355 x = 0;
356 return true;
358 return false;
361 const Stmt* Plugin::getParentStmt( const Stmt* stmt )
363 auto parentsRange = compiler.getASTContext().getParents(*stmt);
364 if ( parentsRange.begin() == parentsRange.end())
365 return nullptr;
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())
373 return nullptr;
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())
383 return nullptr;
385 const Decl *decl = it->get<Decl>();
386 if (decl)
388 if (isa<VarDecl>(decl))
389 return dyn_cast<FunctionDecl>(decl->getDeclContext());
390 return decl;
393 stmt = it->get<Stmt>();
394 if (stmt)
395 return getFunctionDeclContext(context, stmt);
397 return nullptr;
400 const FunctionDecl* Plugin::getParentFunctionDecl( const Stmt* stmt )
402 const Decl *decl = getFunctionDeclContext(compiler.getASTContext(), stmt);
403 if (decl)
404 return static_cast<const FunctionDecl*>(decl->getNonClosureContext());
406 return nullptr;
409 StringRef Plugin::getFilenameOfLocation(SourceLocation spellingLocation) const
411 // prevent crashes when running the global-analysis plugins
412 if (!spellingLocation.isValid())
413 return "";
415 static enum { NOINIT, STDIN, GOOD } s_Mode(NOINIT);
416 if (s_Mode == GOOD)
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();
424 return bufferName;
426 else
428 char const*const pCXX = getenv("CXX");
429 if (pCXX && strstr(pCXX, "sccache"))
430 { // heuristic; sccache passes file with -frewrite-directives by name
431 s_Mode = STDIN;
432 return getFilenameOfLocation(spellingLocation);
434 auto const fn(compiler.getSourceManager().getFilename(spellingLocation));
435 if (!fn.data()) // wtf? happens in sot/source/sdstor/stg.cxx
437 return fn;
439 #if !defined _WIN32
440 assert(fn.startswith("/") || fn == "<stdin>");
441 #endif
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(
487 range.getBegin());
488 auto const end = compiler.getSourceManager().getExpansionLoc(
489 range.getEnd());
490 assert(begin.isFileID() && end.isFileID());
491 if (!(begin == end
492 || compiler.getSourceManager().isBeforeInTranslationUnit(
493 begin, end)))
495 if (isDebugMode()) {
496 report(
497 DiagnosticsEngine::Fatal,
498 ("unexpected broken range for Plugin::containsPreprocessingConditionalInclusion,"
499 " case 1"),
500 range.getBegin())
501 << range;
503 // Conservatively assume "yes" if lexing fails:
504 return true;
506 auto hash = false;
507 for (auto loc = begin;;) {
508 Token tok;
509 if (Lexer::getRawToken(
510 loc, tok, compiler.getSourceManager(),
511 compiler.getLangOpts(), true))
513 if (isDebugMode()) {
514 report(
515 DiagnosticsEngine::Fatal,
516 ("unexpected broken range for"
517 " Plugin::containsPreprocessingConditionalInclusion, case 2"),
518 loc)
519 << range;
521 // Conservatively assume "yes" if lexing fails:
522 return true;
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")
529 return true;
532 if (loc == end) {
533 break;
535 hash = tok.is(tok::hash) && tok.isAtStartOfLine();
536 loc = loc.getLocWithOffset(
537 std::max<unsigned>(
538 Lexer::MeasureTokenLength(
539 loc, compiler.getSourceManager(),
540 compiler.getLangOpts()),
541 1));
543 return false;
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)
559 return false;
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)
565 return true;
566 if (std::search(p1, p2, comment2, comment2 + strlen(comment2)) != p2)
567 return true;
569 return false;
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;
586 APSInt x1, x2;
587 if (evaluate(argument1, x1) && evaluate(argument2, x2))
589 return x1 == 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) {
615 break;
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();
627 continue;
629 if (ce1->getNumArgs() == 0 && ce2->getNumArgs() == 0) {
630 return IdenticalDefaultArgumentsResult::Yes;
632 break;
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;
640 if (isDebugMode()) {
641 report(
642 DiagnosticsEngine::Fatal, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
643 argument1->getExprLoc())
644 << argument1->getSourceRange();
645 report(
646 DiagnosticsEngine::Note, "TODO: second argument is here", argument2->getExprLoc())
647 << argument2->getSourceRange();
648 argument1->dump();
649 argument2->dump();
651 return IdenticalDefaultArgumentsResult::Maybe;
654 RewritePlugin::RewritePlugin( const InstantiationData& data )
655 : Plugin( data )
656 , rewriter( data.rewriter )
660 bool RewritePlugin::insertText( SourceLocation Loc, StringRef Str, bool InsertAfter, bool indentNewLines )
662 assert( rewriter );
663 if (wouldRewriteWorkdir(Loc))
664 return false;
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());
669 return false;
671 if( rewriter->InsertText( Loc, Str, InsertAfter, indentNewLines ))
672 return reportEditFailure( Loc );
673 handler.addSourceModification(Range);
674 return true;
677 bool RewritePlugin::insertTextAfter( SourceLocation Loc, StringRef Str )
679 assert( rewriter );
680 if (wouldRewriteWorkdir(Loc))
681 return false;
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());
686 return false;
688 if( rewriter->InsertTextAfter( Loc, Str ))
689 return reportEditFailure( Loc );
690 handler.addSourceModification(Range);
691 return true;
694 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc, StringRef Str )
696 assert( rewriter );
697 if (wouldRewriteWorkdir(Loc))
698 return false;
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());
703 return false;
705 if( rewriter->InsertTextAfterToken( Loc, Str ))
706 return reportEditFailure( Loc );
707 handler.addSourceModification(Range);
708 return true;
711 bool RewritePlugin::insertTextBefore( SourceLocation Loc, StringRef Str )
713 assert( rewriter );
714 if (wouldRewriteWorkdir(Loc))
715 return false;
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());
720 return false;
722 if( rewriter->InsertTextBefore( Loc, Str ))
723 return reportEditFailure( Loc );
724 handler.addSourceModification(Range);
725 return true;
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 )
741 assert( rewriter );
742 if (wouldRewriteWorkdir(range.getBegin()))
743 return false;
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());
749 return false;
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());
759 return true;
762 bool RewritePlugin::adjustRangeForOptions( CharSourceRange* range, RewriteOptions opts )
764 assert( rewriter );
765 SourceManager& SM = rewriter->getSourceMgr();
766 SourceLocation fileStartLoc = SM.getLocForStartOfFile( SM.getFileID( range->getBegin()));
767 if( fileStartLoc.isInvalid())
768 return false;
769 bool isInvalid = false;
770 const char* fileBuf = SM.getCharacterData( fileStartLoc, &isInvalid );
771 if( isInvalid )
772 return false;
773 const char* startBuf = SM.getCharacterData( range->getBegin(), &isInvalid );
774 if( isInvalid )
775 return false;
776 SourceLocation locationEnd = range->getEnd();
777 if( range->isTokenRange())
778 locationEnd = locationAfterToken( locationEnd );
779 const char* endBuf = SM.getCharacterData( locationEnd, &isInvalid );
780 if( isInvalid )
781 return false;
782 const char* startPos = startBuf;
783 --startPos;
784 while( startPos >= fileBuf && ( *startPos == ' ' || *startPos == '\t' ))
785 --startPos;
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' )
790 ++endPos;
791 if( opts.flags & RemoveWholeStatement )
793 if( *endPos == ';' )
794 ++endPos;
795 else
796 return false;
798 *range = CharSourceRange( SourceRange( range->getBegin().getLocWithOffset( startPos - startBuf + 1 ),
799 locationEnd.getLocWithOffset( endPos - endBuf )), false );
800 return true;
803 bool RewritePlugin::replaceText( SourceLocation Start, unsigned OrigLength, StringRef NewStr )
805 assert( rewriter );
806 if (wouldRewriteWorkdir(Start))
807 return false;
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 );
812 return false;
814 if( rewriter->ReplaceText( Start, OrigLength, NewStr ))
815 return reportEditFailure( Start );
816 handler.addSourceModification(Range);
817 return true;
820 bool RewritePlugin::replaceText( SourceRange range, StringRef NewStr )
822 assert( rewriter );
823 if (wouldRewriteWorkdir(range.getBegin()))
824 return false;
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());
830 return false;
832 if( rewriter->ReplaceText( range, NewStr ))
833 return reportEditFailure( range.getBegin());
834 handler.addSourceModification(range);
835 return true;
838 bool RewritePlugin::replaceText( SourceRange range, SourceRange replacementRange )
840 assert( rewriter );
841 if (wouldRewriteWorkdir(range.getBegin()))
842 return false;
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());
848 return false;
850 if( rewriter->ReplaceText( range, replacementRange ))
851 return reportEditFailure( range.getBegin());
852 handler.addSourceModification(range);
853 return true;
856 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc)
858 if (loc.isInvalid() || loc.isMacroID()) {
859 return false;
861 return
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 );
869 return false;
872 namespace {
874 template<typename Fn> bool checkPathname(
875 StringRef pathname, StringRef against, Fn check)
877 if (check(pathname, against)) {
878 return true;
880 #if defined _WIN32
881 for (std::size_t n = 0;;)
883 std::size_t n1 = pathname.find('\\', n);
884 std::size_t n2 = against.find('\\', n);
885 if (n1 <= n2) {
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))
892 break;
894 n = n1 + 1;
895 } else {
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))
902 break;
904 n = n2 + 1;
907 #endif
908 return false;
913 bool hasPathnamePrefix(StringRef pathname, StringRef prefix)
915 return checkPathname(
916 pathname, prefix,
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))) {
930 return true;
932 buf.clear();
933 return isSamePathname(fullPathname, (SDKDIR "/include/" + includePathname).toStringRef(buf));
936 bool hasCLanguageLinkageType(FunctionDecl const * decl) {
937 assert(decl != nullptr);
938 if (decl->isExternC()) {
939 return true;
941 if (decl->isInExternCContext()) {
942 return true;
944 return false;
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();
960 else
961 break;
962 } while(true);
963 auto recordType = dyn_cast<RecordType>(t);
964 if (!recordType)
965 return nullptr;
966 return dyn_cast_or_null<CXXRecordDecl>(recordType->getDecl());
969 int derivedFromCount(const CXXRecordDecl* subclassRecordDecl, const CXXRecordDecl* baseclassRecordDecl)
971 if (!subclassRecordDecl || !baseclassRecordDecl)
972 return 0;
973 int derivedCount = 0;
974 if (subclassRecordDecl == baseclassRecordDecl)
975 derivedCount++;
976 if (!subclassRecordDecl->hasDefinition())
977 return derivedCount;
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)
983 return derivedCount;
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)
990 return derivedCount;
992 return derivedCount;
995 int derivedFromCount(QualType subclassQt, QualType baseclassQt)
997 auto baseclassRecordDecl = stripTypeSugar(baseclassQt);
998 if (!baseclassRecordDecl)
999 return 0;
1000 auto subclassRecordDecl = stripTypeSugar(subclassQt);
1001 if (!subclassRecordDecl)
1002 return 0;
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) {
1013 return false;
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()) {
1021 return true;
1023 if (auto prev = decl->getPreviousDecl()) {
1024 return hasExternalLinkage(prev);
1026 return !decl->isInAnonymousNamespace();
1029 return true;
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
1036 // type:
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();
1044 return true;
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())
1063 return true;
1065 return false;
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
1072 // type:
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();
1080 return true;
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())
1099 return true;
1101 return false;
1105 } // namespace
1107 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */