diff options
Diffstat (limited to 'compilerplugins/clang/plugin.cxx')
-rw-r--r-- | compilerplugins/clang/plugin.cxx | 296 |
1 files changed, 281 insertions, 15 deletions
diff --git a/compilerplugins/clang/plugin.cxx b/compilerplugins/clang/plugin.cxx index 4e640cfb2c3b..8d8207d30437 100644 --- a/compilerplugins/clang/plugin.cxx +++ b/compilerplugins/clang/plugin.cxx @@ -15,6 +15,7 @@ #include <cstddef> #include <string> +#include <clang/AST/ParentMapContext.h> #include <clang/Basic/FileManager.h> #include <clang/Lex/Lexer.h> @@ -24,10 +25,6 @@ #include "pluginhandler.hxx" #include "check.hxx" -#if CLANG_VERSION >= 110000 -#include "clang/AST/ParentMapContext.h" -#endif - /* Base classes for plugin actions. */ @@ -38,7 +35,7 @@ namespace { Expr const * skipImplicit(Expr const * expr) { if (auto const e = dyn_cast<MaterializeTemporaryExpr>(expr)) { - expr = compat::getSubExpr(e)->IgnoreImpCasts(); + expr = e->getSubExpr()->IgnoreImpCasts(); } if (auto const e = dyn_cast<CXXBindTemporaryExpr>(expr)) { expr = e->getSubExpr(); @@ -97,10 +94,14 @@ bool structurallyIdentical(Stmt const * stmt1, Stmt const * stmt2) { break; case Stmt::MaterializeTemporaryExprClass: case Stmt::CXXBindTemporaryExprClass: + case Stmt::CXXDefaultArgExprClass: case Stmt::ParenExprClass: break; case Stmt::CXXNullPtrLiteralExprClass: return true; + case Stmt::StringLiteralClass: + return cast<clang::StringLiteral>(stmt1)->getBytes() + == cast<clang::StringLiteral>(stmt2)->getBytes(); default: // Conservatively assume non-identical for expressions that don't happen for us in practice // when compiling the LO code base (and for which the above set of supported classes would @@ -131,6 +132,183 @@ DiagnosticBuilder Plugin::report( DiagnosticsEngine::Level level, StringRef mess return handler.report( level, name, message, compiler, loc ); } +bool Plugin::suppressWarningAt(SourceLocation location) const { + auto const start = compiler.getSourceManager().getSpellingLoc(location); + auto const startInfo = compiler.getSourceManager().getDecomposedLoc(start); + auto invalid = false; + auto const buf = compiler.getSourceManager().getBufferData(startInfo.first, &invalid); + if (invalid) { + if (isDebugMode()) { + report(DiagnosticsEngine::Fatal, "failed to getBufferData", start); + } + return false; + } + auto const label = std::string("[-loplugin:").append(name).append("]"); + // Look back to the beginning of the previous line: + auto loc = start; + auto locInfo = startInfo; + auto cur = loc; + enum class State { Normal, Slash, Comment }; + auto state = State::Normal; + auto newlines = 0; + for (auto prev = cur;;) { + auto prevInfo = compiler.getSourceManager().getDecomposedLoc(prev); + if (prev == compiler.getSourceManager().getLocForStartOfFile(prevInfo.first)) { + if (state == State::Comment && isDebugMode()) { + report( + DiagnosticsEngine::Fatal, + "beginning of file while looking for beginning of comment", prev); + } + break; + } + Token tok; + if (Lexer::getRawToken( + Lexer::GetBeginningOfToken( + prev.getLocWithOffset(-1), compiler.getSourceManager(), compiler.getLangOpts()), + tok, compiler.getSourceManager(), compiler.getLangOpts(), true)) + { + if (isDebugMode()) { + report( + DiagnosticsEngine::Fatal, "failed to getRawToken", + prev.getLocWithOffset(-1)); + } + break; + } + if (tok.getLocation() == cur) { + // Keep walking back, character by character, through whitespace preceding the current + // token, which Clang treats as nominally belonging to that token (so the above + // Lexer::getRawToken/Lexer::GetBeginningOfToken will have produced just the same tok + // again): + prev = prev.getLocWithOffset(-1); + continue; + } + cur = tok.getLocation(); + prev = cur; + if (state == State::Comment) { + // Lexer::GetBeginningOfToken (at least towards Clang 15, still) only re-scans from the + // start of the current line, so if we saw the end of a multi-line /*...*/ comment, we + // saw that as individual '/' and '*' faux-tokens, at which point we must (hopefully?) + // actually be at the end of such a multi-line comment, so we keep walking back to the + // first '/*' we encounter (TODO: which still need not be the real start of the comment, + // if the comment contains embedded '/*', but we could determine that only if we + // re-scanned from the start of the file): + if (!tok.is(tok::comment)) { + continue; + } + SmallVector<char, 256> tmp; + bool invalid = false; + auto const spell = Lexer::getSpelling( + prev, tmp, compiler.getSourceManager(), compiler.getLangOpts(), &invalid); + if (invalid) { + if (isDebugMode()) { + report(DiagnosticsEngine::Fatal, "failed to getSpelling", prev); + } + } else if (!compat::starts_with(spell, "/*")) { + continue; + } + } + prevInfo = compiler.getSourceManager().getDecomposedLoc(prev); + auto const end = prev.getLocWithOffset(tok.getLength()); + auto const endInfo = compiler.getSourceManager().getDecomposedLoc(end); + assert(prevInfo.first == endInfo .first); + assert(prevInfo.second <= endInfo.second); + assert(endInfo.first == locInfo.first); + // Whitespace between tokens is found at the end of prev, from end to loc (unless this is a + // multi-line comment, in which case the whitespace has already been inspected as the + // whitespace following the comment's final '/' faux-token): + StringRef ws; + if (state != State::Comment) { + assert(endInfo.second <= locInfo.second); + ws = buf.substr(endInfo.second, locInfo.second - endInfo.second); + } + for (std::size_t i = 0;;) { + auto const j = ws.find('\n', i); + if (j == StringRef::npos) { + break; + } + ++newlines; + if (newlines == 2) { + break; + } + i = j + 1; + } + if (newlines == 2) { + break; + } + auto str = buf.substr(prevInfo.second, endInfo.second - prevInfo.second); + if (tok.is(tok::comment) && str.contains(label)) { + return true; + } + for (std::size_t i = 0;;) { + auto const j = str.find('\n', i); + if (j == StringRef::npos) { + break; + } + ++newlines; + if (newlines == 2) { + break; + } + i = j + 1; + } + if (newlines == 2) { + break; + } + loc = prev; + locInfo = prevInfo; + switch (state) { + case State::Normal: + if (tok.is(tok::slash)) { + state = State::Slash; + } + break; + case State::Slash: + state = tok.is(tok::star) && ws.empty() ? State::Comment : State::Normal; + //TODO: check for "ws is only folding whitespace" rather than for `ws.empty()` (but + // then, we must not count newlines in that whitespace twice, first as part of the + // whitespace following the comment's semi-final '*' faux-token and then as part of + // the comment token's content) + break; + case State::Comment: + state = State::Normal; + } + } + // Look forward to the end of the current line: + loc = start; + locInfo = startInfo; + for (;;) { + Token tok; + if (Lexer::getRawToken(loc, tok, compiler.getSourceManager(), compiler.getLangOpts(), true)) + { + if (isDebugMode()) { + report(DiagnosticsEngine::Fatal, "failed to getRawToken", loc); + } + break; + } + // Whitespace between tokens is found at the beginning, from loc to beg: + auto const beg = tok.getLocation(); + auto const begInfo = compiler.getSourceManager().getDecomposedLoc(beg); + assert(begInfo.first == locInfo.first); + assert(begInfo.second >= locInfo.second); + if (buf.substr(locInfo.second, begInfo.second - locInfo.second).contains('\n')) { + break; + } + auto const next = beg.getLocWithOffset(tok.getLength()); + auto const nextInfo = compiler.getSourceManager().getDecomposedLoc(next); + assert(nextInfo.first == begInfo.first); + assert(nextInfo.second >= begInfo.second); + auto const str = buf.substr(begInfo.second, nextInfo.second - begInfo.second); + if (tok.is(tok::comment) && str.contains(label)) { + return true; + } + if (tok.is(tok::eof) || str.contains('\n')) { + break; + } + loc = next; + locInfo = nextInfo; + } + return false; +} + void normalizeDotDotInFilePath( std::string & s ) { for (std::string::size_type i = 0;;) @@ -251,13 +429,19 @@ StringRef Plugin::getFilenameOfLocation(SourceLocation spellingLocation) const } else { + char const*const pCXX = getenv("CXX"); + if (pCXX && strstr(pCXX, "sccache")) + { // heuristic; sccache passes file with -frewrite-directives by name + s_Mode = STDIN; + return getFilenameOfLocation(spellingLocation); + } auto const fn(compiler.getSourceManager().getFilename(spellingLocation)); if (!fn.data()) // wtf? happens in sot/source/sdstor/stg.cxx { return fn; } #if !defined _WIN32 - assert(fn.startswith("/") || fn == "<stdin>"); + assert(compat::starts_with(fn, "/") || fn == "<stdin>"); #endif s_Mode = fn == "<stdin>" ? STDIN : GOOD; return getFilenameOfLocation(spellingLocation); @@ -278,9 +462,9 @@ bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation) const || hasPathnamePrefix(name, SRCDIR "/include/rtl/") || hasPathnamePrefix(name, SRCDIR "/include/sal/") || hasPathnamePrefix(name, SRCDIR "/include/salhelper/") - || hasPathnamePrefix(name, SRCDIR "/include/systools/") || hasPathnamePrefix(name, SRCDIR "/include/typelib/") - || hasPathnamePrefix(name, SRCDIR "/include/uno/")); + || hasPathnamePrefix(name, SRCDIR "/include/uno/") + || hasPathnamePrefix(name, SDKDIR "/include/")); } bool Plugin::isInUnoIncludeFile(const FunctionDecl* functionDecl) const @@ -363,6 +547,32 @@ bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range) return false; } +bool Plugin::containsComment(SourceRange range) +{ + SourceManager& SM = compiler.getSourceManager(); + SourceLocation startLoc = range.getBegin(); + SourceLocation endLoc = range.getEnd(); + char const* p1 = SM.getCharacterData(startLoc); + char const* p2 = SM.getCharacterData(endLoc); + p2 += Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts()); + + // when doing 'make solenv.check' we don't want the special comments in the + // unit test files to trigger this check + constexpr char const comment0[] = "// expected-error"; + if (std::search(p1, p2, comment0, comment0 + strlen(comment0)) != p2) + return false; + + // check for comments + constexpr char const comment1[] = "/*"; + constexpr char const comment2[] = "//"; + if (std::search(p1, p2, comment1, comment1 + strlen(comment1)) != p2) + return true; + if (std::search(p1, p2, comment2, comment2 + strlen(comment2)) != p2) + return true; + + return false; +} + Plugin::IdenticalDefaultArgumentsResult Plugin::checkIdenticalDefaultArguments( Expr const * argument1, Expr const * argument2) { @@ -652,9 +862,8 @@ bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc) if (loc.isInvalid() || loc.isMacroID()) { return false; } - return - getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(loc)) - .startswith(WORKDIR "/"); + return compat::starts_with( + getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(loc)), WORKDIR "/"); } bool RewritePlugin::reportEditFailure( SourceLocation loc ) @@ -708,7 +917,7 @@ bool hasPathnamePrefix(StringRef pathname, StringRef prefix) { return checkPathname( pathname, prefix, - [](StringRef p, StringRef a) { return p.startswith(a); }); + [](StringRef p, StringRef a) { return compat::starts_with(p, a); }); } bool isSamePathname(StringRef pathname, StringRef other) @@ -717,6 +926,16 @@ bool isSamePathname(StringRef pathname, StringRef other) pathname, other, [](StringRef p, StringRef a) { return p == a; }); } +bool isSameUnoIncludePathname(StringRef fullPathname, StringRef includePathname) +{ + llvm::SmallVector<char, 256> buf; + if (isSamePathname(fullPathname, (SRCDIR "/include/" + includePathname).toStringRef(buf))) { + return true; + } + buf.clear(); + return isSamePathname(fullPathname, (SDKDIR "/include/" + includePathname).toStringRef(buf)); +} + bool hasCLanguageLinkageType(FunctionDecl const * decl) { assert(decl != nullptr); if (decl->isExternC()) { @@ -731,8 +950,19 @@ bool hasCLanguageLinkageType(FunctionDecl const * decl) { static const CXXRecordDecl* stripTypeSugar(QualType qt) { const clang::Type* t = qt.getTypePtr(); - while (auto elaboratedType = dyn_cast<ElaboratedType>(t)) - t = elaboratedType->desugar().getTypePtr(); + do + { + if (auto elaboratedType = dyn_cast<ElaboratedType>(t)) + t = elaboratedType->desugar().getTypePtr(); + else if (auto tsType = dyn_cast<TemplateSpecializationType>(t)) + t = tsType->desugar().getTypePtr(); + else if (auto sttpType = dyn_cast<SubstTemplateTypeParmType>(t)) + t = sttpType->desugar().getTypePtr(); + else if (auto tdType = dyn_cast<TypedefType>(t)) + t = tdType->desugar().getTypePtr(); + else + break; + } while(true); auto recordType = dyn_cast<RecordType>(t); if (!recordType) return nullptr; @@ -782,7 +1012,7 @@ int derivedFromCount(QualType subclassQt, QualType baseclassQt) // a variable declared in an 'extern "..." {...}'-style linkage-specification as // if it contained the 'extern' specifier: bool hasExternalLinkage(VarDecl const * decl) { - if (decl->getLinkageAndVisibility().getLinkage() != ExternalLinkage) { + if (decl->getLinkageAndVisibility().getLinkage() != compat::Linkage::External) { return false; } for (auto ctx = decl->getLexicalDeclContext(); @@ -802,6 +1032,42 @@ bool hasExternalLinkage(VarDecl const * decl) { return true; } +bool isSmartPointerType(QualType qt) +{ + // First check whether the object type as written is, or is derived from, std::unique_ptr or + // std::shared_ptr, in case the get member function is declared at a base class of that std + // type: + if (loplugin::isDerivedFrom( + qt->getAsCXXRecordDecl(), + [](Decl const * decl) { + auto const dc = loplugin::DeclCheck(decl); + return dc.ClassOrStruct("unique_ptr").StdNamespace() + || dc.ClassOrStruct("shared_ptr").StdNamespace(); + })) + return true; + + // Then check the object type coerced to the type of the get member function, in + // case the type-as-written is derived from one of these types (tools::SvRef is + // final, but the rest are not): + auto const tc2 = loplugin::TypeCheck(qt); + if (tc2.ClassOrStruct("unique_ptr").StdNamespace() + || tc2.ClassOrStruct("shared_ptr").StdNamespace() + || tc2.Class("Reference").Namespace("uno").Namespace("star") + .Namespace("sun").Namespace("com").GlobalNamespace() + || tc2.Class("Reference").Namespace("rtl").GlobalNamespace() + || tc2.Class("SvRef").Namespace("tools").GlobalNamespace() + || tc2.Class("WeakReference").Namespace("tools").GlobalNamespace() + || tc2.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace() + || tc2.Class("ScopedVclPtrInstance").GlobalNamespace() + || tc2.Class("VclPtr").GlobalNamespace() + || tc2.Class("ScopedVclPtr").GlobalNamespace() + || tc2.Class("intrusive_ptr").Namespace("boost").GlobalNamespace()) + { + return true; + } + return false; +} + bool isSmartPointerType(const Expr* e) { // First check whether the object type as written is, or is derived from, std::unique_ptr or |