summaryrefslogtreecommitdiff
path: root/compilerplugins/clang/plugin.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/plugin.cxx')
-rw-r--r--compilerplugins/clang/plugin.cxx296
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