Version 24.2.2.2, tag libreoffice-24.2.2.2
[LibreOffice.git] / compilerplugins / clang / plugin.cxx
blob10102d4260790deeb61630366dfa157841b2879c
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::CXXDefaultArgExprClass:
98 case Stmt::ParenExprClass:
99 break;
100 case Stmt::CXXNullPtrLiteralExprClass:
101 return true;
102 case Stmt::StringLiteralClass:
103 return cast<clang::StringLiteral>(stmt1)->getBytes()
104 == cast<clang::StringLiteral>(stmt2)->getBytes();
105 default:
106 // Conservatively assume non-identical for expressions that don't happen for us in practice
107 // when compiling the LO code base (and for which the above set of supported classes would
108 // need to be extended):
109 return false;
111 auto i1 = stmt1->child_begin();
112 auto e1 = stmt1->child_end();
113 auto i2 = stmt2->child_begin();
114 auto e2 = stmt2->child_end();
115 for (; i1 != e1; ++i1, ++i2) {
116 if (i2 == e2 || !structurallyIdentical(*i1, *i2)) {
117 return false;
120 return i2 == e2;
125 Plugin::Plugin( const InstantiationData& data )
126 : compiler( data.compiler ), handler( data.handler ), name( data.name )
130 DiagnosticBuilder Plugin::report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc ) const
132 return handler.report( level, name, message, compiler, loc );
135 bool Plugin::suppressWarningAt(SourceLocation location) const {
136 auto const start = compiler.getSourceManager().getSpellingLoc(location);
137 auto const startInfo = compiler.getSourceManager().getDecomposedLoc(start);
138 auto invalid = false;
139 auto const buf = compiler.getSourceManager().getBufferData(startInfo.first, &invalid);
140 if (invalid) {
141 if (isDebugMode()) {
142 report(DiagnosticsEngine::Fatal, "failed to getBufferData", start);
144 return false;
146 auto const label = std::string("[-loplugin:").append(name).append("]");
147 // Look back to the beginning of the previous line:
148 auto loc = start;
149 auto locInfo = startInfo;
150 auto cur = loc;
151 enum class State { Normal, Slash, Comment };
152 auto state = State::Normal;
153 auto newlines = 0;
154 for (auto prev = cur;;) {
155 auto prevInfo = compiler.getSourceManager().getDecomposedLoc(prev);
156 if (prev == compiler.getSourceManager().getLocForStartOfFile(prevInfo.first)) {
157 if (state == State::Comment && isDebugMode()) {
158 report(
159 DiagnosticsEngine::Fatal,
160 "beginning of file while looking for beginning of comment", prev);
162 break;
164 Token tok;
165 if (Lexer::getRawToken(
166 Lexer::GetBeginningOfToken(
167 prev.getLocWithOffset(-1), compiler.getSourceManager(), compiler.getLangOpts()),
168 tok, compiler.getSourceManager(), compiler.getLangOpts(), true))
170 if (isDebugMode()) {
171 report(
172 DiagnosticsEngine::Fatal, "failed to getRawToken",
173 prev.getLocWithOffset(-1));
175 break;
177 if (tok.getLocation() == cur) {
178 // Keep walking back, character by character, through whitespace preceding the current
179 // token, which Clang treats as nominally belonging to that token (so the above
180 // Lexer::getRawToken/Lexer::GetBeginningOfToken will have produced just the same tok
181 // again):
182 prev = prev.getLocWithOffset(-1);
183 continue;
185 cur = tok.getLocation();
186 prev = cur;
187 if (state == State::Comment) {
188 // Lexer::GetBeginningOfToken (at least towards Clang 15, still) only re-scans from the
189 // start of the current line, so if we saw the end of a multi-line /*...*/ comment, we
190 // saw that as individual '/' and '*' faux-tokens, at which point we must (hopefully?)
191 // actually be at the end of such a multi-line comment, so we keep walking back to the
192 // first '/*' we encounter (TODO: which still need not be the real start of the comment,
193 // if the comment contains embedded '/*', but we could determine that only if we
194 // re-scanned from the start of the file):
195 if (!tok.is(tok::comment)) {
196 continue;
198 SmallVector<char, 256> tmp;
199 bool invalid = false;
200 auto const spell = Lexer::getSpelling(
201 prev, tmp, compiler.getSourceManager(), compiler.getLangOpts(), &invalid);
202 if (invalid) {
203 if (isDebugMode()) {
204 report(DiagnosticsEngine::Fatal, "failed to getSpelling", prev);
206 } else if (!spell.startswith("/*")) {
207 continue;
210 prevInfo = compiler.getSourceManager().getDecomposedLoc(prev);
211 auto const end = prev.getLocWithOffset(tok.getLength());
212 auto const endInfo = compiler.getSourceManager().getDecomposedLoc(end);
213 assert(prevInfo.first == endInfo .first);
214 assert(prevInfo.second <= endInfo.second);
215 assert(endInfo.first == locInfo.first);
216 // Whitespace between tokens is found at the end of prev, from end to loc (unless this is a
217 // multi-line comment, in which case the whitespace has already been inspected as the
218 // whitespace following the comment's final '/' faux-token):
219 StringRef ws;
220 if (state != State::Comment) {
221 assert(endInfo.second <= locInfo.second);
222 ws = buf.substr(endInfo.second, locInfo.second - endInfo.second);
224 for (std::size_t i = 0;;) {
225 auto const j = ws.find('\n', i);
226 if (j == StringRef::npos) {
227 break;
229 ++newlines;
230 if (newlines == 2) {
231 break;
233 i = j + 1;
235 if (newlines == 2) {
236 break;
238 auto str = buf.substr(prevInfo.second, endInfo.second - prevInfo.second);
239 if (tok.is(tok::comment) && str.contains(label)) {
240 return true;
242 for (std::size_t i = 0;;) {
243 auto const j = str.find('\n', i);
244 if (j == StringRef::npos) {
245 break;
247 ++newlines;
248 if (newlines == 2) {
249 break;
251 i = j + 1;
253 if (newlines == 2) {
254 break;
256 loc = prev;
257 locInfo = prevInfo;
258 switch (state) {
259 case State::Normal:
260 if (tok.is(tok::slash)) {
261 state = State::Slash;
263 break;
264 case State::Slash:
265 state = tok.is(tok::star) && ws.empty() ? State::Comment : State::Normal;
266 //TODO: check for "ws is only folding whitespace" rather than for `ws.empty()` (but
267 // then, we must not count newlines in that whitespace twice, first as part of the
268 // whitespace following the comment's semi-final '*' faux-token and then as part of
269 // the comment token's content)
270 break;
271 case State::Comment:
272 state = State::Normal;
275 // Look forward to the end of the current line:
276 loc = start;
277 locInfo = startInfo;
278 for (;;) {
279 Token tok;
280 if (Lexer::getRawToken(loc, tok, compiler.getSourceManager(), compiler.getLangOpts(), true))
282 if (isDebugMode()) {
283 report(DiagnosticsEngine::Fatal, "failed to getRawToken", loc);
285 break;
287 // Whitespace between tokens is found at the beginning, from loc to beg:
288 auto const beg = tok.getLocation();
289 auto const begInfo = compiler.getSourceManager().getDecomposedLoc(beg);
290 assert(begInfo.first == locInfo.first);
291 assert(begInfo.second >= locInfo.second);
292 if (buf.substr(locInfo.second, begInfo.second - locInfo.second).contains('\n')) {
293 break;
295 auto const next = beg.getLocWithOffset(tok.getLength());
296 auto const nextInfo = compiler.getSourceManager().getDecomposedLoc(next);
297 assert(nextInfo.first == begInfo.first);
298 assert(nextInfo.second >= begInfo.second);
299 auto const str = buf.substr(begInfo.second, nextInfo.second - begInfo.second);
300 if (tok.is(tok::comment) && str.contains(label)) {
301 return true;
303 if (tok.is(tok::eof) || str.contains('\n')) {
304 break;
306 loc = next;
307 locInfo = nextInfo;
309 return false;
312 void normalizeDotDotInFilePath( std::string & s )
314 for (std::string::size_type i = 0;;)
316 i = s.find("/.", i);
317 if (i == std::string::npos) {
318 break;
320 if (i + 2 == s.length() || s[i + 2] == '/') {
321 s.erase(i, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
322 } else if (s[i + 2] == '.'
323 && (i + 3 == s.length() || s[i + 3] == '/'))
325 if (i == 0) { // /..[/CCC] -> /..[/CCC]
326 break;
328 auto j = s.rfind('/', i - 1);
329 if (j == std::string::npos)
331 // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC ->
332 // CCC, to avoid wrong ../../CCC -> CCC; relative paths
333 // shouldn't happen anyway, and even if they did, wouldn't
334 // match against WORKDIR anyway, as WORKDIR should be
335 // absolute):
336 break;
338 s.erase(j, i + 3 - j); // AAA/BBB/..[/CCC] -> AAA[/CCC]
339 i = j;
340 } else {
341 i += 2;
346 void Plugin::registerPlugin( Plugin* (*create)( const InstantiationData& ), const char* optionName,
347 bool isPPCallback, bool isSharedPlugin, bool byDefault )
349 PluginHandler::registerPlugin( create, optionName, isPPCallback, isSharedPlugin, byDefault );
352 bool Plugin::evaluate(const Expr* expr, APSInt& x)
354 if (compat::EvaluateAsInt(expr, x, compiler.getASTContext()))
356 return true;
358 if (isa<CXXNullPtrLiteralExpr>(expr)) {
359 x = 0;
360 return true;
362 return false;
365 const Stmt* Plugin::getParentStmt( const Stmt* stmt )
367 auto parentsRange = compiler.getASTContext().getParents(*stmt);
368 if ( parentsRange.begin() == parentsRange.end())
369 return nullptr;
370 return parentsRange.begin()->get<Stmt>();
373 Stmt* Plugin::getParentStmt( Stmt* stmt )
375 auto parentsRange = compiler.getASTContext().getParents(*stmt);
376 if ( parentsRange.begin() == parentsRange.end())
377 return nullptr;
378 return const_cast<Stmt*>(parentsRange.begin()->get<Stmt>());
381 const Decl* getFunctionDeclContext(ASTContext& context, const Stmt* stmt)
383 auto const parents = context.getParents(*stmt);
384 auto it = parents.begin();
386 if (it == parents.end())
387 return nullptr;
389 const Decl *decl = it->get<Decl>();
390 if (decl)
392 if (isa<VarDecl>(decl))
393 return dyn_cast<FunctionDecl>(decl->getDeclContext());
394 return decl;
397 stmt = it->get<Stmt>();
398 if (stmt)
399 return getFunctionDeclContext(context, stmt);
401 return nullptr;
404 const FunctionDecl* Plugin::getParentFunctionDecl( const Stmt* stmt )
406 const Decl *decl = getFunctionDeclContext(compiler.getASTContext(), stmt);
407 if (decl)
408 return static_cast<const FunctionDecl*>(decl->getNonClosureContext());
410 return nullptr;
413 StringRef Plugin::getFilenameOfLocation(SourceLocation spellingLocation) const
415 // prevent crashes when running the global-analysis plugins
416 if (!spellingLocation.isValid())
417 return "";
419 static enum { NOINIT, STDIN, GOOD } s_Mode(NOINIT);
420 if (s_Mode == GOOD)
422 return compiler.getSourceManager().getFilename(spellingLocation);
424 else if (s_Mode == STDIN
425 || !compiler.getSourceManager().isInMainFile(spellingLocation))
427 const char* bufferName = compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename();
428 return bufferName;
430 else
432 char const*const pCXX = getenv("CXX");
433 if (pCXX && strstr(pCXX, "sccache"))
434 { // heuristic; sccache passes file with -frewrite-directives by name
435 s_Mode = STDIN;
436 return getFilenameOfLocation(spellingLocation);
438 auto const fn(compiler.getSourceManager().getFilename(spellingLocation));
439 if (!fn.data()) // wtf? happens in sot/source/sdstor/stg.cxx
441 return fn;
443 #if !defined _WIN32
444 assert(fn.startswith("/") || fn == "<stdin>");
445 #endif
446 s_Mode = fn == "<stdin>" ? STDIN : GOOD;
447 return getFilenameOfLocation(spellingLocation);
451 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation) const
453 StringRef name{ getFilenameOfLocation(spellingLocation) };
454 return compiler.getSourceManager().isInMainFile(spellingLocation)
455 ? (isSamePathname(name, SRCDIR "/cppu/source/cppu/compat.cxx")
456 || isSamePathname(name, SRCDIR "/cppuhelper/source/compat.cxx")
457 || isSamePathname(name, SRCDIR "/sal/osl/all/compat.cxx"))
458 : (hasPathnamePrefix(name, SRCDIR "/include/com/")
459 || hasPathnamePrefix(name, SRCDIR "/include/cppu/")
460 || hasPathnamePrefix(name, SRCDIR "/include/cppuhelper/")
461 || hasPathnamePrefix(name, SRCDIR "/include/osl/")
462 || hasPathnamePrefix(name, SRCDIR "/include/rtl/")
463 || hasPathnamePrefix(name, SRCDIR "/include/sal/")
464 || hasPathnamePrefix(name, SRCDIR "/include/salhelper/")
465 || hasPathnamePrefix(name, SRCDIR "/include/typelib/")
466 || hasPathnamePrefix(name, SRCDIR "/include/uno/")
467 || hasPathnamePrefix(name, SDKDIR "/include/"));
470 bool Plugin::isInUnoIncludeFile(const FunctionDecl* functionDecl) const
472 return isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(
473 functionDecl->getCanonicalDecl()->getNameInfo().getLoc()));
476 SourceLocation Plugin::locationAfterToken( SourceLocation location )
478 return Lexer::getLocForEndOfToken( location, 0, compiler.getSourceManager(), compiler.getLangOpts());
481 bool Plugin::isUnitTestMode()
483 return PluginHandler::isUnitTestMode();
486 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range)
488 // Preprocessing directives (other than _Pragma, which is not relevant here) cannot appear in
489 // macro expansions, so it is safe to just consider the range of expansion locations:
490 auto const begin = compiler.getSourceManager().getExpansionLoc(
491 range.getBegin());
492 auto const end = compiler.getSourceManager().getExpansionLoc(
493 range.getEnd());
494 assert(begin.isFileID() && end.isFileID());
495 if (!(begin == end
496 || compiler.getSourceManager().isBeforeInTranslationUnit(
497 begin, end)))
499 if (isDebugMode()) {
500 report(
501 DiagnosticsEngine::Fatal,
502 ("unexpected broken range for Plugin::containsPreprocessingConditionalInclusion,"
503 " case 1"),
504 range.getBegin())
505 << range;
507 // Conservatively assume "yes" if lexing fails:
508 return true;
510 auto hash = false;
511 for (auto loc = begin;;) {
512 Token tok;
513 if (Lexer::getRawToken(
514 loc, tok, compiler.getSourceManager(),
515 compiler.getLangOpts(), true))
517 if (isDebugMode()) {
518 report(
519 DiagnosticsEngine::Fatal,
520 ("unexpected broken range for"
521 " Plugin::containsPreprocessingConditionalInclusion, case 2"),
522 loc)
523 << range;
525 // Conservatively assume "yes" if lexing fails:
526 return true;
528 if (hash && tok.is(tok::raw_identifier)) {
529 auto const id = tok.getRawIdentifier();
530 if (id == "if" || id == "ifdef" || id == "ifndef"
531 || id == "elif" || id == "else" || id == "endif")
533 return true;
536 if (loc == end) {
537 break;
539 hash = tok.is(tok::hash) && tok.isAtStartOfLine();
540 loc = loc.getLocWithOffset(
541 std::max<unsigned>(
542 Lexer::MeasureTokenLength(
543 loc, compiler.getSourceManager(),
544 compiler.getLangOpts()),
545 1));
547 return false;
550 bool Plugin::containsComment(SourceRange range)
552 SourceManager& SM = compiler.getSourceManager();
553 SourceLocation startLoc = range.getBegin();
554 SourceLocation endLoc = range.getEnd();
555 char const* p1 = SM.getCharacterData(startLoc);
556 char const* p2 = SM.getCharacterData(endLoc);
557 p2 += Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
559 // when doing 'make solenv.check' we don't want the special comments in the
560 // unit test files to trigger this check
561 constexpr char const comment0[] = "// expected-error";
562 if (std::search(p1, p2, comment0, comment0 + strlen(comment0)) != p2)
563 return false;
565 // check for comments
566 constexpr char const comment1[] = "/*";
567 constexpr char const comment2[] = "//";
568 if (std::search(p1, p2, comment1, comment1 + strlen(comment1)) != p2)
569 return true;
570 if (std::search(p1, p2, comment2, comment2 + strlen(comment2)) != p2)
571 return true;
573 return false;
576 Plugin::IdenticalDefaultArgumentsResult Plugin::checkIdenticalDefaultArguments(
577 Expr const * argument1, Expr const * argument2)
579 if ((argument1 == nullptr) != (argument2 == nullptr)) {
580 return IdenticalDefaultArgumentsResult::No;
582 if (argument1 == nullptr) {
583 return IdenticalDefaultArgumentsResult::Yes;
585 if (argument1->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent)
586 && argument2->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent))
588 return IdenticalDefaultArgumentsResult::Yes;
590 APSInt x1, x2;
591 if (evaluate(argument1, x1) && evaluate(argument2, x2))
593 return x1 == x2
594 ? IdenticalDefaultArgumentsResult::Yes
595 : IdenticalDefaultArgumentsResult::No;
597 APFloat f1(0.0f), f2(0.0f);
598 if (argument1->EvaluateAsFloat(f1, compiler.getASTContext())
599 && argument2->EvaluateAsFloat(f2, compiler.getASTContext()))
601 return f1.bitwiseIsEqual(f2)
602 ? IdenticalDefaultArgumentsResult::Yes
603 : IdenticalDefaultArgumentsResult::No;
605 auto const desugared1 = argument1->IgnoreParenImpCasts();
606 auto const desugared2 = argument2->IgnoreParenImpCasts();
607 if (auto const lit1 = dyn_cast<clang::StringLiteral>(desugared1)) {
608 if (auto const lit2 = dyn_cast<clang::StringLiteral>(desugared2)) {
609 return lit1->getBytes() == lit2->getBytes()
610 ? IdenticalDefaultArgumentsResult::Yes
611 : IdenticalDefaultArgumentsResult::No;
614 // catch params with defaults like "= OUString()"
615 for (Expr const * e1 = desugared1, * e2 = desugared2;;) {
616 auto const ce1 = dyn_cast<CXXConstructExpr>(skipImplicit(e1));
617 auto const ce2 = dyn_cast<CXXConstructExpr>(skipImplicit(e2));
618 if (ce1 == nullptr || ce2 == nullptr) {
619 break;
621 if (ce1->getConstructor()->getCanonicalDecl() != ce2->getConstructor()->getCanonicalDecl())
623 return IdenticalDefaultArgumentsResult::No;
625 if (ce1->isElidable() && ce2->isElidable() && ce1->getNumArgs() == 1
626 && ce2->getNumArgs() == 1)
628 assert(ce1->getConstructor()->isCopyOrMoveConstructor());
629 e1 = ce1->getArg(0)->IgnoreImpCasts();
630 e2 = ce2->getArg(0)->IgnoreImpCasts();
631 continue;
633 if (ce1->getNumArgs() == 0 && ce2->getNumArgs() == 0) {
634 return IdenticalDefaultArgumentsResult::Yes;
636 break;
638 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
639 // function template specializations that happen to not have been instantiated in this TU, try a
640 // structural comparison of the arguments:
641 if (structurallyIdentical(argument1, argument2)) {
642 return IdenticalDefaultArgumentsResult::Yes;
644 if (isDebugMode()) {
645 report(
646 DiagnosticsEngine::Fatal, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
647 argument1->getExprLoc())
648 << argument1->getSourceRange();
649 report(
650 DiagnosticsEngine::Note, "TODO: second argument is here", argument2->getExprLoc())
651 << argument2->getSourceRange();
652 argument1->dump();
653 argument2->dump();
655 return IdenticalDefaultArgumentsResult::Maybe;
658 RewritePlugin::RewritePlugin( const InstantiationData& data )
659 : Plugin( data )
660 , rewriter( data.rewriter )
664 bool RewritePlugin::insertText( SourceLocation Loc, StringRef Str, bool InsertAfter, bool indentNewLines )
666 assert( rewriter );
667 if (wouldRewriteWorkdir(Loc))
668 return false;
669 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
670 if( !handler.checkOverlap( Range ) )
672 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
673 return false;
675 if( rewriter->InsertText( Loc, Str, InsertAfter, indentNewLines ))
676 return reportEditFailure( Loc );
677 handler.addSourceModification(Range);
678 return true;
681 bool RewritePlugin::insertTextAfter( SourceLocation Loc, StringRef Str )
683 assert( rewriter );
684 if (wouldRewriteWorkdir(Loc))
685 return false;
686 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
687 if( !handler.checkOverlap( Range ) )
689 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
690 return false;
692 if( rewriter->InsertTextAfter( Loc, Str ))
693 return reportEditFailure( Loc );
694 handler.addSourceModification(Range);
695 return true;
698 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc, StringRef Str )
700 assert( rewriter );
701 if (wouldRewriteWorkdir(Loc))
702 return false;
703 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
704 if( !handler.checkOverlap( Range ) )
706 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
707 return false;
709 if( rewriter->InsertTextAfterToken( Loc, Str ))
710 return reportEditFailure( Loc );
711 handler.addSourceModification(Range);
712 return true;
715 bool RewritePlugin::insertTextBefore( SourceLocation Loc, StringRef Str )
717 assert( rewriter );
718 if (wouldRewriteWorkdir(Loc))
719 return false;
720 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
721 if( !handler.checkOverlap( Range ) )
723 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
724 return false;
726 if( rewriter->InsertTextBefore( Loc, Str ))
727 return reportEditFailure( Loc );
728 handler.addSourceModification(Range);
729 return true;
732 bool RewritePlugin::removeText( SourceLocation Start, unsigned Length, RewriteOptions opts )
734 CharSourceRange range( SourceRange( Start, Start.getLocWithOffset( Length )), false );
735 return removeText( range, opts );
738 bool RewritePlugin::removeText( SourceRange range, RewriteOptions opts )
740 return removeText( CharSourceRange( range, true ), opts );
743 bool RewritePlugin::removeText( CharSourceRange range, RewriteOptions opts )
745 assert( rewriter );
746 if (wouldRewriteWorkdir(range.getBegin()))
747 return false;
748 if( rewriter->getRangeSize( range, opts ) == -1 )
749 return reportEditFailure( range.getBegin());
750 if( !handler.checkOverlap( range.getAsRange() ) )
752 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", range.getBegin());
753 return false;
755 if( opts.flags & RemoveWholeStatement || opts.flags & RemoveAllWhitespace )
757 if( !adjustRangeForOptions( &range, opts ))
758 return reportEditFailure( range.getBegin());
760 if( rewriter->RemoveText( range, opts ))
761 return reportEditFailure( range.getBegin());
762 handler.addSourceModification(range.getAsRange());
763 return true;
766 bool RewritePlugin::adjustRangeForOptions( CharSourceRange* range, RewriteOptions opts )
768 assert( rewriter );
769 SourceManager& SM = rewriter->getSourceMgr();
770 SourceLocation fileStartLoc = SM.getLocForStartOfFile( SM.getFileID( range->getBegin()));
771 if( fileStartLoc.isInvalid())
772 return false;
773 bool isInvalid = false;
774 const char* fileBuf = SM.getCharacterData( fileStartLoc, &isInvalid );
775 if( isInvalid )
776 return false;
777 const char* startBuf = SM.getCharacterData( range->getBegin(), &isInvalid );
778 if( isInvalid )
779 return false;
780 SourceLocation locationEnd = range->getEnd();
781 if( range->isTokenRange())
782 locationEnd = locationAfterToken( locationEnd );
783 const char* endBuf = SM.getCharacterData( locationEnd, &isInvalid );
784 if( isInvalid )
785 return false;
786 const char* startPos = startBuf;
787 --startPos;
788 while( startPos >= fileBuf && ( *startPos == ' ' || *startPos == '\t' ))
789 --startPos;
790 if( startPos >= fileBuf && *startPos == '\n' )
791 startPos = startBuf - 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
792 const char* endPos = endBuf;
793 while( *endPos == ' ' || *endPos == '\t' )
794 ++endPos;
795 if( opts.flags & RemoveWholeStatement )
797 if( *endPos == ';' )
798 ++endPos;
799 else
800 return false;
802 *range = CharSourceRange( SourceRange( range->getBegin().getLocWithOffset( startPos - startBuf + 1 ),
803 locationEnd.getLocWithOffset( endPos - endBuf )), false );
804 return true;
807 bool RewritePlugin::replaceText( SourceLocation Start, unsigned OrigLength, StringRef NewStr )
809 assert( rewriter );
810 if (wouldRewriteWorkdir(Start))
811 return false;
812 SourceRange Range(Start, Start.getLocWithOffset(std::max<size_t>(OrigLength, NewStr.size())));
813 if( OrigLength != 0 && !handler.checkOverlap( Range ) )
815 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", Start );
816 return false;
818 if( rewriter->ReplaceText( Start, OrigLength, NewStr ))
819 return reportEditFailure( Start );
820 handler.addSourceModification(Range);
821 return true;
824 bool RewritePlugin::replaceText( SourceRange range, StringRef NewStr )
826 assert( rewriter );
827 if (wouldRewriteWorkdir(range.getBegin()))
828 return false;
829 if( rewriter->getRangeSize( range ) == -1 )
830 return reportEditFailure( range.getBegin());
831 if( !handler.checkOverlap( range ) )
833 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
834 return false;
836 if( rewriter->ReplaceText( range, NewStr ))
837 return reportEditFailure( range.getBegin());
838 handler.addSourceModification(range);
839 return true;
842 bool RewritePlugin::replaceText( SourceRange range, SourceRange replacementRange )
844 assert( rewriter );
845 if (wouldRewriteWorkdir(range.getBegin()))
846 return false;
847 if( rewriter->getRangeSize( range ) == -1 )
848 return reportEditFailure( range.getBegin());
849 if( !handler.checkOverlap( range ) )
851 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
852 return false;
854 if( rewriter->ReplaceText( range, replacementRange ))
855 return reportEditFailure( range.getBegin());
856 handler.addSourceModification(range);
857 return true;
860 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc)
862 if (loc.isInvalid() || loc.isMacroID()) {
863 return false;
865 return
866 getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(loc))
867 .startswith(WORKDIR "/");
870 bool RewritePlugin::reportEditFailure( SourceLocation loc )
872 report( DiagnosticsEngine::Warning, "cannot perform source modification (macro expansion involved?)", loc );
873 return false;
876 namespace {
878 template<typename Fn> bool checkPathname(
879 StringRef pathname, StringRef against, Fn check)
881 if (check(pathname, against)) {
882 return true;
884 #if defined _WIN32
885 for (std::size_t n = 0;;)
887 std::size_t n1 = pathname.find('\\', n);
888 std::size_t n2 = against.find('\\', n);
889 if (n1 <= n2) {
890 if (n1 >= against.size()) {
891 return check(pathname.substr(n), against.substr(n));
893 if ((against[n1] != '/' && against[n1] != '\\')
894 || pathname.substr(n, n1 - n) != against.substr(n, n1 - n))
896 break;
898 n = n1 + 1;
899 } else {
900 if (n2 >= pathname.size()) {
901 return check(pathname.substr(n), against.substr(n));
903 if (pathname[n2] != '/'
904 || pathname.substr(n, n2 - n) != against.substr(n, n2 - n))
906 break;
908 n = n2 + 1;
911 #endif
912 return false;
917 bool hasPathnamePrefix(StringRef pathname, StringRef prefix)
919 return checkPathname(
920 pathname, prefix,
921 [](StringRef p, StringRef a) { return p.startswith(a); });
924 bool isSamePathname(StringRef pathname, StringRef other)
926 return checkPathname(
927 pathname, other, [](StringRef p, StringRef a) { return p == a; });
930 bool isSameUnoIncludePathname(StringRef fullPathname, StringRef includePathname)
932 llvm::SmallVector<char, 256> buf;
933 if (isSamePathname(fullPathname, (SRCDIR "/include/" + includePathname).toStringRef(buf))) {
934 return true;
936 buf.clear();
937 return isSamePathname(fullPathname, (SDKDIR "/include/" + includePathname).toStringRef(buf));
940 bool hasCLanguageLinkageType(FunctionDecl const * decl) {
941 assert(decl != nullptr);
942 if (decl->isExternC()) {
943 return true;
945 if (decl->isInExternCContext()) {
946 return true;
948 return false;
951 static const CXXRecordDecl* stripTypeSugar(QualType qt)
953 const clang::Type* t = qt.getTypePtr();
956 if (auto elaboratedType = dyn_cast<ElaboratedType>(t))
957 t = elaboratedType->desugar().getTypePtr();
958 else if (auto tsType = dyn_cast<TemplateSpecializationType>(t))
959 t = tsType->desugar().getTypePtr();
960 else if (auto sttpType = dyn_cast<SubstTemplateTypeParmType>(t))
961 t = sttpType->desugar().getTypePtr();
962 else if (auto tdType = dyn_cast<TypedefType>(t))
963 t = tdType->desugar().getTypePtr();
964 else
965 break;
966 } while(true);
967 auto recordType = dyn_cast<RecordType>(t);
968 if (!recordType)
969 return nullptr;
970 return dyn_cast_or_null<CXXRecordDecl>(recordType->getDecl());
973 int derivedFromCount(const CXXRecordDecl* subclassRecordDecl, const CXXRecordDecl* baseclassRecordDecl)
975 if (!subclassRecordDecl || !baseclassRecordDecl)
976 return 0;
977 int derivedCount = 0;
978 if (subclassRecordDecl == baseclassRecordDecl)
979 derivedCount++;
980 if (!subclassRecordDecl->hasDefinition())
981 return derivedCount;
982 for (auto it = subclassRecordDecl->bases_begin(); it != subclassRecordDecl->bases_end(); ++it)
984 derivedCount += derivedFromCount(stripTypeSugar(it->getType()), baseclassRecordDecl);
985 // short-circuit, we only care about 0,1,2
986 if (derivedCount > 1)
987 return derivedCount;
989 for (auto it = subclassRecordDecl->vbases_begin(); it != subclassRecordDecl->vbases_end(); ++it)
991 derivedCount += derivedFromCount(stripTypeSugar(it->getType()), baseclassRecordDecl);
992 // short-circuit, we only care about 0,1,2
993 if (derivedCount > 1)
994 return derivedCount;
996 return derivedCount;
999 int derivedFromCount(QualType subclassQt, QualType baseclassQt)
1001 auto baseclassRecordDecl = stripTypeSugar(baseclassQt);
1002 if (!baseclassRecordDecl)
1003 return 0;
1004 auto subclassRecordDecl = stripTypeSugar(subclassQt);
1005 if (!subclassRecordDecl)
1006 return 0;
1008 return derivedFromCount(subclassRecordDecl, baseclassRecordDecl);
1011 // It looks like Clang wrongly implements DR 4
1012 // (<http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#4>) and treats
1013 // a variable declared in an 'extern "..." {...}'-style linkage-specification as
1014 // if it contained the 'extern' specifier:
1015 bool hasExternalLinkage(VarDecl const * decl) {
1016 if (decl->getLinkageAndVisibility().getLinkage() != compat::Linkage::External) {
1017 return false;
1019 for (auto ctx = decl->getLexicalDeclContext();
1020 ctx->getDeclKind() != Decl::TranslationUnit;
1021 ctx = ctx->getLexicalParent())
1023 if (auto ls = dyn_cast<LinkageSpecDecl>(ctx)) {
1024 if (!ls->hasBraces()) {
1025 return true;
1027 if (auto prev = decl->getPreviousDecl()) {
1028 return hasExternalLinkage(prev);
1030 return !decl->isInAnonymousNamespace();
1033 return true;
1036 bool isSmartPointerType(QualType qt)
1038 // First check whether the object type as written is, or is derived from, std::unique_ptr or
1039 // std::shared_ptr, in case the get member function is declared at a base class of that std
1040 // type:
1041 if (loplugin::isDerivedFrom(
1042 qt->getAsCXXRecordDecl(),
1043 [](Decl const * decl) {
1044 auto const dc = loplugin::DeclCheck(decl);
1045 return dc.ClassOrStruct("unique_ptr").StdNamespace()
1046 || dc.ClassOrStruct("shared_ptr").StdNamespace();
1048 return true;
1050 // Then check the object type coerced to the type of the get member function, in
1051 // case the type-as-written is derived from one of these types (tools::SvRef is
1052 // final, but the rest are not):
1053 auto const tc2 = loplugin::TypeCheck(qt);
1054 if (tc2.ClassOrStruct("unique_ptr").StdNamespace()
1055 || tc2.ClassOrStruct("shared_ptr").StdNamespace()
1056 || tc2.Class("Reference").Namespace("uno").Namespace("star")
1057 .Namespace("sun").Namespace("com").GlobalNamespace()
1058 || tc2.Class("Reference").Namespace("rtl").GlobalNamespace()
1059 || tc2.Class("SvRef").Namespace("tools").GlobalNamespace()
1060 || tc2.Class("WeakReference").Namespace("tools").GlobalNamespace()
1061 || tc2.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace()
1062 || tc2.Class("ScopedVclPtrInstance").GlobalNamespace()
1063 || tc2.Class("VclPtr").GlobalNamespace()
1064 || tc2.Class("ScopedVclPtr").GlobalNamespace()
1065 || tc2.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
1067 return true;
1069 return false;
1072 bool isSmartPointerType(const Expr* e)
1074 // First check whether the object type as written is, or is derived from, std::unique_ptr or
1075 // std::shared_ptr, in case the get member function is declared at a base class of that std
1076 // type:
1077 if (loplugin::isDerivedFrom(
1078 e->IgnoreImpCasts()->getType()->getAsCXXRecordDecl(),
1079 [](Decl const * decl) {
1080 auto const dc = loplugin::DeclCheck(decl);
1081 return dc.ClassOrStruct("unique_ptr").StdNamespace()
1082 || dc.ClassOrStruct("shared_ptr").StdNamespace();
1084 return true;
1086 // Then check the object type coerced to the type of the get member function, in
1087 // case the type-as-written is derived from one of these types (tools::SvRef is
1088 // final, but the rest are not):
1089 auto const tc2 = loplugin::TypeCheck(e->getType());
1090 if (tc2.ClassOrStruct("unique_ptr").StdNamespace()
1091 || tc2.ClassOrStruct("shared_ptr").StdNamespace()
1092 || tc2.Class("Reference").Namespace("uno").Namespace("star")
1093 .Namespace("sun").Namespace("com").GlobalNamespace()
1094 || tc2.Class("Reference").Namespace("rtl").GlobalNamespace()
1095 || tc2.Class("SvRef").Namespace("tools").GlobalNamespace()
1096 || tc2.Class("WeakReference").Namespace("tools").GlobalNamespace()
1097 || tc2.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace()
1098 || tc2.Class("ScopedVclPtrInstance").GlobalNamespace()
1099 || tc2.Class("VclPtr").GlobalNamespace()
1100 || tc2.Class("ScopedVclPtr").GlobalNamespace()
1101 || tc2.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
1103 return true;
1105 return false;
1109 } // namespace
1111 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */