/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * Based on LLVM/Clang. * * This file is distributed under the University of Illinois Open Source * License. See LICENSE.TXT for details. *
*/
/* Base classes for plugin actions.
*/ namespace loplugin
{
namespace {
Expr const * skipImplicit(Expr const * expr) { if (autoconst e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
expr = e->getSubExpr()->IgnoreImpCasts();
} if (autoconst e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
expr = e->getSubExpr();
} return expr;
}
bool structurallyIdentical(Stmt const * stmt1, Stmt const * stmt2) { if (stmt1->getStmtClass() != stmt2->getStmtClass()) { returnfalse;
} switch (stmt1->getStmtClass()) { case Stmt::CXXConstructExprClass: if (cast<CXXConstructExpr>(stmt1)->getConstructor()->getCanonicalDecl()
!= cast<CXXConstructExpr>(stmt2)->getConstructor()->getCanonicalDecl())
{ returnfalse;
} break; case Stmt::DeclRefExprClass: if (cast<DeclRefExpr>(stmt1)->getDecl()->getCanonicalDecl()
!= cast<DeclRefExpr>(stmt2)->getDecl()->getCanonicalDecl())
{ returnfalse;
} break; case Stmt::ImplicitCastExprClass:
{ autoconst e1 = cast<ImplicitCastExpr>(stmt1); autoconst e2 = cast<ImplicitCastExpr>(stmt2); if (e1->getCastKind() != e2->getCastKind()
|| e1->getType().getCanonicalType() != e2->getType().getCanonicalType())
{ returnfalse;
} break;
} case Stmt::MemberExprClass:
{ autoconst e1 = cast<MemberExpr>(stmt1); autoconst e2 = cast<MemberExpr>(stmt2); if (e1->isArrow() != e2->isArrow()
|| e1->getType().getCanonicalType() != e2->getType().getCanonicalType())
{ returnfalse;
} break;
} case Stmt::CXXMemberCallExprClass: case Stmt::CXXOperatorCallExprClass: if (cast<Expr>(stmt1)->getType().getCanonicalType()
!= cast<Expr>(stmt2)->getType().getCanonicalType())
{ returnfalse;
} break; case Stmt::MaterializeTemporaryExprClass: case Stmt::CXXBindTemporaryExprClass: case Stmt::CXXDefaultArgExprClass: case Stmt::ParenExprClass: break; case Stmt::CXXNullPtrLiteralExprClass: returntrue; 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 // need to be extended): returnfalse;
} auto i1 = stmt1->child_begin(); auto e1 = stmt1->child_end(); auto i2 = stmt2->child_begin(); auto e2 = stmt2->child_end(); for (; i1 != e1; ++i1, ++i2) { if (i2 == e2 || !structurallyIdentical(*i1, *i2)) { returnfalse;
}
} return i2 == e2;
}
DiagnosticBuilder Plugin::report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc ) const
{ return handler.report( level, name, message, compiler, loc );
}
bool Plugin::suppressWarningAt(SourceLocation location) const { autoconst start = compiler.getSourceManager().getSpellingLoc(location); autoconst startInfo = compiler.getSourceManager().getDecomposedLoc(start); auto invalid = false; autoconst buf = compiler.getSourceManager().getBufferData(startInfo.first, &invalid); if (invalid) { if (isDebugMode()) {
report(DiagnosticsEngine::Fatal, "failed to getBufferData", start);
} returnfalse;
} autoconst 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; enumclass 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; autoconst spell = Lexer::getSpelling(
prev, tmp, compiler.getSourceManager(), compiler.getLangOpts(), &invalid); if (invalid) { if (isDebugMode()) {
report(DiagnosticsEngine::Fatal, "failed to getSpelling", prev);
}
} elseif (!compat::starts_with(spell, "/*")) { continue;
}
}
prevInfo = compiler.getSourceManager().getDecomposedLoc(prev); autoconst end = prev.getLocWithOffset(tok.getLength()); autoconst 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;;) { autoconst 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)) { returntrue;
} for (std::size_t i = 0;;) { autoconst 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: autoconst beg = tok.getLocation(); autoconst 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;
} autoconst next = beg.getLocWithOffset(tok.getLength()); autoconst nextInfo = compiler.getSourceManager().getDecomposedLoc(next);
assert(nextInfo.first == begInfo.first);
assert(nextInfo.second >= begInfo.second); autoconst str = buf.substr(begInfo.second, nextInfo.second - begInfo.second); if (tok.is(tok::comment) && str.contains(label)) { returntrue;
} if (tok.is(tok::eof) || str.contains('\n')) { break;
}
loc = next;
locInfo = nextInfo;
} returnfalse;
}
void normalizeDotDotInFilePath( std::string & s )
{ for (std::string::size_type i = 0;;)
{
i = s.find("/.", i); if (i == std::string::npos) { break;
} if (i + 2 == s.length() || s[i + 2] == '/') {
s.erase(i, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
} elseif (s[i + 2] == '.'
&& (i + 3 == s.length() || s[i + 3] == '/'))
{ if (i == 0) { // /..[/CCC] -> /..[/CCC] break;
} auto j = s.rfind('/', i - 1); if (j == std::string::npos)
{ // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC -> // CCC, to avoid wrong ../../CCC -> CCC; relative paths // shouldn't happen anyway, and even if they did, wouldn't // match against WORKDIR anyway, as WORKDIR should be // absolute): break;
}
s.erase(j, i + 3 - j); // AAA/BBB/..[/CCC] -> AAA[/CCC]
i = j;
} else {
i += 2;
}
}
}
bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range)
{ // Preprocessing directives (other than _Pragma, which is not relevant here) cannot appear in // macro expansions, so it is safe to just consider the range of expansion locations: autoconst begin = compiler.getSourceManager().getExpansionLoc(
range.getBegin()); autoconst end = compiler.getSourceManager().getExpansionLoc(
range.getEnd());
assert(begin.isFileID() && end.isFileID()); if (!(begin == end
|| compiler.getSourceManager().isBeforeInTranslationUnit(
begin, end)))
{ if (isDebugMode()) {
report(
DiagnosticsEngine::Fatal,
("unexpected broken range for Plugin::containsPreprocessingConditionalInclusion," " case 1"),
range.getBegin())
<< range;
} // Conservatively assume "yes" if lexing fails: returntrue;
} auto hash = false; for (auto loc = begin;;) {
Token tok; if (Lexer::getRawToken(
loc, tok, compiler.getSourceManager(),
compiler.getLangOpts(), true))
{ if (isDebugMode()) {
report(
DiagnosticsEngine::Fatal,
("unexpected broken range for" " Plugin::containsPreprocessingConditionalInclusion, case 2"),
loc)
<< range;
} // Conservatively assume "yes" if lexing fails: returntrue;
} if (hash && tok.is(tok::raw_identifier)) { autoconst id = tok.getRawIdentifier(); if (id == "if" || id == "ifdef" || id == "ifndef"
|| id == "elif" || id == "else" || id == "endif")
{ returntrue;
}
} if (loc == end) { break;
}
hash = tok.is(tok::hash) && tok.isAtStartOfLine();
loc = loc.getLocWithOffset(
std::max<unsigned>(
Lexer::MeasureTokenLength(
loc, compiler.getSourceManager(),
compiler.getLangOpts()),
1));
} returnfalse;
}
// when doing 'make solenv.check' we don't want the special comments in the // unit test files to trigger this check
constexpr charconst comment0[] = "// expected-error"; if (std::search(p1, p2, comment0, comment0 + strlen(comment0)) != p2) returnfalse;
staticconst CXXRecordDecl* stripTypeSugar(QualType qt)
{ const clang::Type* t = qt.getTypePtr(); do
{ if (auto elaboratedType = dyn_cast<ElaboratedType>(t))
t = elaboratedType->desugar().getTypePtr(); elseif (auto tsType = dyn_cast<TemplateSpecializationType>(t))
t = tsType->desugar().getTypePtr(); elseif (auto sttpType = dyn_cast<SubstTemplateTypeParmType>(t))
t = sttpType->desugar().getTypePtr(); elseif (auto tdType = dyn_cast<TypedefType>(t))
t = tdType->desugar().getTypePtr(); else break;
} while(true); auto recordType = dyn_cast<RecordType>(t); if (!recordType) return nullptr; return dyn_cast_or_null<CXXRecordDecl>(recordType->getDecl());
}
int derivedFromCount(const CXXRecordDecl* subclassRecordDecl, const CXXRecordDecl* baseclassRecordDecl)
{ if (!subclassRecordDecl || !baseclassRecordDecl) return 0; int derivedCount = 0; if (subclassRecordDecl == baseclassRecordDecl)
derivedCount++; if (!subclassRecordDecl->hasDefinition()) return derivedCount; for (auto it = subclassRecordDecl->bases_begin(); it != subclassRecordDecl->bases_end(); ++it)
{
derivedCount += derivedFromCount(stripTypeSugar(it->getType()), baseclassRecordDecl); // short-circuit, we only care about 0,1,2 if (derivedCount > 1) return derivedCount;
} for (auto it = subclassRecordDecl->vbases_begin(); it != subclassRecordDecl->vbases_end(); ++it)
{
derivedCount += derivedFromCount(stripTypeSugar(it->getType()), baseclassRecordDecl); // short-circuit, we only care about 0,1,2 if (derivedCount > 1) return derivedCount;
} return derivedCount;
}
int derivedFromCount(QualType subclassQt, QualType baseclassQt)
{ auto baseclassRecordDecl = stripTypeSugar(baseclassQt); if (!baseclassRecordDecl) return 0; auto subclassRecordDecl = stripTypeSugar(subclassQt); if (!subclassRecordDecl) return 0;
// It looks like Clang wrongly implements DR 4 // (<http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#4>) and treats // 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() != compat::Linkage::External) { returnfalse;
} for (auto ctx = decl->getLexicalDeclContext();
ctx->getDeclKind() != Decl::TranslationUnit;
ctx = ctx->getLexicalParent())
{ if (auto ls = dyn_cast<LinkageSpecDecl>(ctx)) { if (!ls->hasBraces()) { returntrue;
} if (auto prev = decl->getPreviousDecl()) { return hasExternalLinkage(prev);
} return !decl->isInAnonymousNamespace();
}
} returntrue;
}
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) { autoconst dc = loplugin::DeclCheck(decl); return dc.ClassOrStruct("unique_ptr").StdNamespace()
|| dc.ClassOrStruct("shared_ptr").StdNamespace();
})) returntrue;
// 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): autoconst 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())
{ returntrue;
} returnfalse;
}
bool isSmartPointerType(const Expr* e)
{ // 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(
e->IgnoreImpCasts()->getType()->getAsCXXRecordDecl(),
[](Decl const * decl) { autoconst dc = loplugin::DeclCheck(decl); return dc.ClassOrStruct("unique_ptr").StdNamespace()
|| dc.ClassOrStruct("shared_ptr").StdNamespace();
})) returntrue;
// 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): autoconst tc2 = loplugin::TypeCheck(e->getType()); 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())
{ returntrue;
} returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.