gecko-dev/build/clang-plugin/clang-plugin.cpp

1274 строки
46 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/Version.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Frontend/MultiplexConsumer.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include <memory>
#define CLANG_VERSION_FULL (CLANG_VERSION_MAJOR * 100 + CLANG_VERSION_MINOR)
using namespace llvm;
using namespace clang;
#if CLANG_VERSION_FULL >= 306
typedef std::unique_ptr<ASTConsumer> ASTConsumerPtr;
#else
typedef ASTConsumer *ASTConsumerPtr;
#endif
namespace {
QualType GetCallReturnType(const CallExpr *expr) {
#if CLANG_VERSION_FULL >= 307
return expr->getCallReturnType(expr->getCalleeDecl()->getASTContext());
#else
return expr->getCallReturnType();
#endif
}
using namespace clang::ast_matchers;
class DiagnosticsMatcher {
public:
DiagnosticsMatcher();
ASTConsumerPtr makeASTConsumer() {
return astMatcher.newASTConsumer();
}
private:
class ScopeChecker : public MatchFinder::MatchCallback {
public:
enum Scope {
eLocal,
eGlobal
};
ScopeChecker(Scope scope_) :
scope(scope_) {}
virtual void run(const MatchFinder::MatchResult &Result);
private:
Scope scope;
};
class NonHeapClassChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class ArithmeticArgChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class TrivialCtorDtorChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class NaNExprChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class NoAddRefReleaseOnReturnChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class RefCountedInsideLambdaChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class NeedsNoVTableTypeChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class NonMemMovableChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
ScopeChecker stackClassChecker;
ScopeChecker globalClassChecker;
NonHeapClassChecker nonheapClassChecker;
ArithmeticArgChecker arithmeticArgChecker;
TrivialCtorDtorChecker trivialCtorDtorChecker;
NaNExprChecker nanExprChecker;
NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
NeedsNoVTableTypeChecker needsNoVTableTypeChecker;
NonMemMovableChecker nonMemMovableChecker;
MatchFinder astMatcher;
};
namespace {
std::string getDeclarationNamespace(const Decl *decl) {
const DeclContext *DC = decl->getDeclContext()->getEnclosingNamespaceContext();
const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
if (!ND) {
return "";
}
while (const DeclContext *ParentDC = ND->getParent()) {
if (!isa<NamespaceDecl>(ParentDC)) {
break;
}
ND = cast<NamespaceDecl>(ParentDC);
}
const auto& name = ND->getName();
return name;
}
bool isInIgnoredNamespaceForImplicitCtor(const Decl *decl) {
std::string name = getDeclarationNamespace(decl);
if (name == "") {
return false;
}
return name == "std" || // standard C++ lib
name == "__gnu_cxx" || // gnu C++ lib
name == "boost" || // boost
name == "webrtc" || // upstream webrtc
name.substr(0, 4) == "icu_" || // icu
name == "google" || // protobuf
name == "google_breakpad" || // breakpad
name == "soundtouch" || // libsoundtouch
name == "stagefright" || // libstagefright
name == "MacFileUtilities" || // MacFileUtilities
name == "dwarf2reader" || // dwarf2reader
name == "arm_ex_to_module" || // arm_ex_to_module
name == "testing"; // gtest
}
bool isInIgnoredNamespaceForImplicitConversion(const Decl *decl) {
std::string name = getDeclarationNamespace(decl);
if (name == "") {
return false;
}
return name == "std" || // standard C++ lib
name == "__gnu_cxx" || // gnu C++ lib
name == "google_breakpad" || // breakpad
name == "testing"; // gtest
}
bool isIgnoredPathForImplicitCtor(const Decl *decl) {
decl = decl->getCanonicalDecl();
SourceLocation Loc = decl->getLocation();
const SourceManager &SM = decl->getASTContext().getSourceManager();
SmallString<1024> FileName = SM.getFilename(Loc);
llvm::sys::fs::make_absolute(FileName);
llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName),
end = llvm::sys::path::rend(FileName);
for (; begin != end; ++begin) {
if (begin->compare_lower(StringRef("skia")) == 0 ||
begin->compare_lower(StringRef("angle")) == 0 ||
begin->compare_lower(StringRef("harfbuzz")) == 0 ||
begin->compare_lower(StringRef("hunspell")) == 0 ||
begin->compare_lower(StringRef("scoped_ptr.h")) == 0 ||
begin->compare_lower(StringRef("graphite2")) == 0) {
return true;
}
if (begin->compare_lower(StringRef("chromium")) == 0) {
// Ignore security/sandbox/chromium but not ipc/chromium.
++begin;
return begin != end && begin->compare_lower(StringRef("sandbox")) == 0;
}
}
return false;
}
bool isIgnoredPathForImplicitConversion(const Decl *decl) {
decl = decl->getCanonicalDecl();
SourceLocation Loc = decl->getLocation();
const SourceManager &SM = decl->getASTContext().getSourceManager();
SmallString<1024> FileName = SM.getFilename(Loc);
llvm::sys::fs::make_absolute(FileName);
llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName),
end = llvm::sys::path::rend(FileName);
for (; begin != end; ++begin) {
if (begin->compare_lower(StringRef("graphite2")) == 0) {
return true;
}
}
return false;
}
bool isInterestingDeclForImplicitCtor(const Decl *decl) {
return !isInIgnoredNamespaceForImplicitCtor(decl) &&
!isIgnoredPathForImplicitCtor(decl);
}
bool isInterestingDeclForImplicitConversion(const Decl *decl) {
return !isInIgnoredNamespaceForImplicitConversion(decl) &&
!isIgnoredPathForImplicitConversion(decl);
}
}
class CustomTypeAnnotation {
enum ReasonKind {
RK_None,
RK_Direct,
RK_ArrayElement,
RK_BaseClass,
RK_Field,
};
struct AnnotationReason {
QualType Type;
ReasonKind Kind;
const FieldDecl *Field;
bool valid() const { return Kind != RK_None; }
};
typedef DenseMap<void *, AnnotationReason> ReasonCache;
const char *Spelling;
const char *Pretty;
ReasonCache Cache;
public:
CustomTypeAnnotation(const char *Spelling, const char *Pretty)
: Spelling(Spelling), Pretty(Pretty) {};
// Checks if this custom annotation "effectively affects" the given type.
bool hasEffectiveAnnotation(QualType T) {
return directAnnotationReason(T).valid();
}
void dumpAnnotationReason(DiagnosticsEngine &Diag, QualType T, SourceLocation Loc);
private:
bool hasLiteralAnnotation(QualType T) const;
AnnotationReason directAnnotationReason(QualType T);
};
static CustomTypeAnnotation StackClass =
CustomTypeAnnotation("moz_stack_class", "stack");
static CustomTypeAnnotation GlobalClass =
CustomTypeAnnotation("moz_global_class", "global");
static CustomTypeAnnotation NonHeapClass =
CustomTypeAnnotation("moz_nonheap_class", "non-heap");
static CustomTypeAnnotation MustUse =
CustomTypeAnnotation("moz_must_use", "must-use");
class MozChecker : public ASTConsumer, public RecursiveASTVisitor<MozChecker> {
DiagnosticsEngine &Diag;
const CompilerInstance &CI;
DiagnosticsMatcher matcher;
public:
MozChecker(const CompilerInstance &CI) : Diag(CI.getDiagnostics()), CI(CI) {}
ASTConsumerPtr getOtherConsumer() {
return matcher.makeASTConsumer();
}
virtual void HandleTranslationUnit(ASTContext &ctx) {
TraverseDecl(ctx.getTranslationUnitDecl());
}
static bool hasCustomAnnotation(const Decl *D, const char *Spelling) {
iterator_range<specific_attr_iterator<AnnotateAttr> > Attrs =
D->specific_attrs<AnnotateAttr>();
for (AnnotateAttr *Attr : Attrs) {
if (Attr->getAnnotation() == Spelling) {
return true;
}
}
return false;
}
void HandleUnusedExprResult(const Stmt *stmt) {
const Expr* E = dyn_cast_or_null<Expr>(stmt);
if (E) {
QualType T = E->getType();
if (MustUse.hasEffectiveAnnotation(T)) {
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "Unused value of must-use type %0");
Diag.Report(E->getLocStart(), errorID) << T;
MustUse.dumpAnnotationReason(Diag, T, E->getLocStart());
}
}
}
bool VisitCXXRecordDecl(CXXRecordDecl *d) {
// We need definitions, not declarations
if (!d->isThisDeclarationADefinition()) return true;
// Look through all of our immediate bases to find methods that need to be
// overridden
typedef std::vector<CXXMethodDecl *> OverridesVector;
OverridesVector must_overrides;
for (CXXRecordDecl::base_class_iterator base = d->bases_begin(),
e = d->bases_end(); base != e; ++base) {
// The base is either a class (CXXRecordDecl) or it's a templated class...
CXXRecordDecl *parent = base->getType()
.getDesugaredType(d->getASTContext())->getAsCXXRecordDecl();
// The parent might not be resolved to a type yet. In this case, we can't
// do any checking here. For complete correctness, we should visit
// template instantiations, but this case is likely to be rare, so we will
// ignore it until it becomes important.
if (!parent) {
continue;
}
parent = parent->getDefinition();
for (CXXRecordDecl::method_iterator M = parent->method_begin();
M != parent->method_end(); ++M) {
if (hasCustomAnnotation(*M, "moz_must_override"))
must_overrides.push_back(*M);
}
}
for (OverridesVector::iterator it = must_overrides.begin();
it != must_overrides.end(); ++it) {
bool overridden = false;
for (CXXRecordDecl::method_iterator M = d->method_begin();
!overridden && M != d->method_end(); ++M) {
// The way that Clang checks if a method M overrides its parent method
// is if the method has the same name but would not overload.
if (M->getName() == (*it)->getName() &&
!CI.getSema().IsOverload(*M, (*it), false)) {
overridden = true;
break;
}
}
if (!overridden) {
unsigned overrideID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "%0 must override %1");
unsigned overrideNote = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "function to override is here");
Diag.Report(d->getLocation(), overrideID) << d->getDeclName() <<
(*it)->getDeclName();
Diag.Report((*it)->getLocation(), overrideNote);
}
}
if (!d->isAbstract() && isInterestingDeclForImplicitCtor(d)) {
for (CXXRecordDecl::ctor_iterator ctor = d->ctor_begin(),
e = d->ctor_end(); ctor != e; ++ctor) {
// Ignore non-converting ctors
if (!ctor->isConvertingConstructor(false)) {
continue;
}
// Ignore copy or move constructors
if (ctor->isCopyOrMoveConstructor()) {
continue;
}
// Ignore deleted constructors
if (ctor->isDeleted()) {
continue;
}
// Ignore whitelisted constructors
if (MozChecker::hasCustomAnnotation(*ctor, "moz_implicit")) {
continue;
}
unsigned ctorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "bad implicit conversion constructor for %0");
unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "consider adding the explicit keyword to the constructor");
Diag.Report(ctor->getLocation(), ctorID) << d->getDeclName();
Diag.Report(ctor->getLocation(), noteID);
}
}
return true;
}
bool VisitSwitchCase(SwitchCase* stmt) {
HandleUnusedExprResult(stmt->getSubStmt());
return true;
}
bool VisitCompoundStmt(CompoundStmt* stmt) {
for (CompoundStmt::body_iterator it = stmt->body_begin(), e = stmt->body_end();
it != e; ++it) {
HandleUnusedExprResult(*it);
}
return true;
}
bool VisitIfStmt(IfStmt* Stmt) {
HandleUnusedExprResult(Stmt->getThen());
HandleUnusedExprResult(Stmt->getElse());
return true;
}
bool VisitWhileStmt(WhileStmt* Stmt) {
HandleUnusedExprResult(Stmt->getBody());
return true;
}
bool VisitDoStmt(DoStmt* Stmt) {
HandleUnusedExprResult(Stmt->getBody());
return true;
}
bool VisitForStmt(ForStmt* Stmt) {
HandleUnusedExprResult(Stmt->getBody());
HandleUnusedExprResult(Stmt->getInit());
HandleUnusedExprResult(Stmt->getInc());
return true;
}
bool VisitBinComma(BinaryOperator* Op) {
HandleUnusedExprResult(Op->getLHS());
return true;
}
};
/// A cached data of whether classes are refcounted or not.
typedef DenseMap<const CXXRecordDecl *,
std::pair<const Decl *, bool> > RefCountedMap;
RefCountedMap refCountedClasses;
bool classHasAddRefRelease(const CXXRecordDecl *D) {
const RefCountedMap::iterator& it = refCountedClasses.find(D);
if (it != refCountedClasses.end()) {
return it->second.second;
}
bool seenAddRef = false;
bool seenRelease = false;
for (CXXRecordDecl::method_iterator method = D->method_begin();
method != D->method_end(); ++method) {
const auto &name = method->getName();
if (name == "AddRef") {
seenAddRef = true;
} else if (name == "Release") {
seenRelease = true;
}
}
refCountedClasses[D] = std::make_pair(D, seenAddRef && seenRelease);
return seenAddRef && seenRelease;
}
bool isClassRefCounted(QualType T);
bool isClassRefCounted(const CXXRecordDecl *D) {
// Normalize so that D points to the definition if it exists.
if (!D->hasDefinition())
return false;
D = D->getDefinition();
// Base class: anyone with AddRef/Release is obviously a refcounted class.
if (classHasAddRefRelease(D))
return true;
// Look through all base cases to figure out if the parent is a refcounted class.
for (CXXRecordDecl::base_class_const_iterator base = D->bases_begin();
base != D->bases_end(); ++base) {
bool super = isClassRefCounted(base->getType());
if (super) {
return true;
}
}
return false;
}
bool isClassRefCounted(QualType T) {
while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
T = arrTy->getElementType();
CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
return clazz ? isClassRefCounted(clazz) : false;
}
/// A cached data of whether classes are memmovable, and if not, what declaration
/// makes them non-movable
typedef DenseMap<const CXXRecordDecl *, const CXXRecordDecl *> InferredMovability;
InferredMovability inferredMovability;
bool isClassNonMemMovable(QualType T);
const CXXRecordDecl* isClassNonMemMovableWorker(QualType T);
const CXXRecordDecl* isClassNonMemMovableWorker(const CXXRecordDecl *D) {
// If we have a definition, then we want to standardize our reference to point
// to the definition node. If we don't have a definition, that means that either
// we only have a forward declaration of the type in our file, or we are being
// passed a template argument which is not used, and thus never instantiated by
// clang.
// As the argument isn't used, we can't memmove it (as we don't know it's size),
// which means not reporting an error is OK.
if (!D->hasDefinition()) {
return 0;
}
D = D->getDefinition();
// Are we explicitly marked as non-memmovable class?
if (MozChecker::hasCustomAnnotation(D, "moz_non_memmovable")) {
return D;
}
// Look through all base cases to figure out if the parent is a non-memmovable class.
for (CXXRecordDecl::base_class_const_iterator base = D->bases_begin();
base != D->bases_end(); ++base) {
const CXXRecordDecl *result = isClassNonMemMovableWorker(base->getType());
if (result) {
return result;
}
}
// Look through all members to figure out if a member is a non-memmovable class.
for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end();
field != e; ++field) {
const CXXRecordDecl *result = isClassNonMemMovableWorker(field->getType());
if (result) {
return result;
}
}
return 0;
}
const CXXRecordDecl* isClassNonMemMovableWorker(QualType T) {
while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
T = arrTy->getElementType();
const CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
return clazz ? isClassNonMemMovableWorker(clazz) : 0;
}
bool isClassNonMemMovable(const CXXRecordDecl *D) {
InferredMovability::iterator it =
inferredMovability.find(D);
if (it != inferredMovability.end())
return !!it->second;
const CXXRecordDecl *result = isClassNonMemMovableWorker(D);
inferredMovability.insert(std::make_pair(D, result));
return !!result;
}
bool isClassNonMemMovable(QualType T) {
while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
T = arrTy->getElementType();
const CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
return clazz ? isClassNonMemMovable(clazz) : false;
}
const CXXRecordDecl* findWhyClassIsNonMemMovable(QualType T) {
while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
T = arrTy->getElementType();
CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
InferredMovability::iterator it =
inferredMovability.find(clazz);
assert(it != inferredMovability.end());
return it->second;
}
template<class T>
bool IsInSystemHeader(const ASTContext &AC, const T &D) {
auto &SourceManager = AC.getSourceManager();
auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart());
if (ExpansionLoc.isInvalid()) {
return false;
}
return SourceManager.isInSystemHeader(ExpansionLoc);
}
bool typeHasVTable(QualType T) {
while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
T = arrTy->getElementType();
CXXRecordDecl* offender = T->getAsCXXRecordDecl();
return offender && offender->hasDefinition() && offender->isDynamicClass();
}
}
namespace clang {
namespace ast_matchers {
/// This matcher will match any class with the stack class assertion or an
/// array of such classes.
AST_MATCHER(QualType, stackClassAggregate) {
return StackClass.hasEffectiveAnnotation(Node);
}
/// This matcher will match any class with the global class assertion or an
/// array of such classes.
AST_MATCHER(QualType, globalClassAggregate) {
return GlobalClass.hasEffectiveAnnotation(Node);
}
/// This matcher will match any class with the stack class assertion or an
/// array of such classes.
AST_MATCHER(QualType, nonheapClassAggregate) {
return NonHeapClass.hasEffectiveAnnotation(Node);
}
/// This matcher will match any function declaration that is declared as a heap
/// allocator.
AST_MATCHER(FunctionDecl, heapAllocator) {
return MozChecker::hasCustomAnnotation(&Node, "moz_heap_allocator");
}
/// This matcher will match any declaration that is marked as not accepting
/// arithmetic expressions in its arguments.
AST_MATCHER(Decl, noArithmeticExprInArgs) {
return MozChecker::hasCustomAnnotation(&Node, "moz_no_arith_expr_in_arg");
}
/// This matcher will match any C++ class that is marked as having a trivial
/// constructor and destructor.
AST_MATCHER(CXXRecordDecl, hasTrivialCtorDtor) {
return MozChecker::hasCustomAnnotation(&Node, "moz_trivial_ctor_dtor");
}
/// This matcher will match any function declaration that is marked to prohibit
/// calling AddRef or Release on its return value.
AST_MATCHER(FunctionDecl, hasNoAddRefReleaseOnReturnAttr) {
return MozChecker::hasCustomAnnotation(&Node, "moz_no_addref_release_on_return");
}
/// This matcher will match all arithmetic binary operators.
AST_MATCHER(BinaryOperator, binaryArithmeticOperator) {
BinaryOperatorKind opcode = Node.getOpcode();
return opcode == BO_Mul ||
opcode == BO_Div ||
opcode == BO_Rem ||
opcode == BO_Add ||
opcode == BO_Sub ||
opcode == BO_Shl ||
opcode == BO_Shr ||
opcode == BO_And ||
opcode == BO_Xor ||
opcode == BO_Or ||
opcode == BO_MulAssign ||
opcode == BO_DivAssign ||
opcode == BO_RemAssign ||
opcode == BO_AddAssign ||
opcode == BO_SubAssign ||
opcode == BO_ShlAssign ||
opcode == BO_ShrAssign ||
opcode == BO_AndAssign ||
opcode == BO_XorAssign ||
opcode == BO_OrAssign;
}
/// This matcher will match all arithmetic unary operators.
AST_MATCHER(UnaryOperator, unaryArithmeticOperator) {
UnaryOperatorKind opcode = Node.getOpcode();
return opcode == UO_PostInc ||
opcode == UO_PostDec ||
opcode == UO_PreInc ||
opcode == UO_PreDec ||
opcode == UO_Plus ||
opcode == UO_Minus ||
opcode == UO_Not;
}
/// This matcher will match == and != binary operators.
AST_MATCHER(BinaryOperator, binaryEqualityOperator) {
BinaryOperatorKind opcode = Node.getOpcode();
return opcode == BO_EQ || opcode == BO_NE;
}
/// This matcher will match floating point types.
AST_MATCHER(QualType, isFloat) {
return Node->isRealFloatingType();
}
/// This matcher will match locations in system headers. This is adopted from
/// isExpansionInSystemHeader in newer clangs, but modified in order to work
/// with old clangs that we use on infra.
AST_MATCHER(BinaryOperator, isInSystemHeader) {
return IsInSystemHeader(Finder->getASTContext(), Node);
}
/// This matcher will match locations in SkScalar.h. This header contains a
/// known NaN-testing expression which we would like to whitelist.
AST_MATCHER(BinaryOperator, isInSkScalarDotH) {
SourceLocation Loc = Node.getOperatorLoc();
auto &SourceManager = Finder->getASTContext().getSourceManager();
SmallString<1024> FileName = SourceManager.getFilename(Loc);
return llvm::sys::path::rbegin(FileName)->equals("SkScalar.h");
}
/// This matcher will match all accesses to AddRef or Release methods.
AST_MATCHER(MemberExpr, isAddRefOrRelease) {
ValueDecl *Member = Node.getMemberDecl();
CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Member);
if (Method) {
const auto &Name = Method->getName();
return Name == "AddRef" || Name == "Release";
}
return false;
}
/// This matcher will select classes which are refcounted.
AST_MATCHER(QualType, isRefCounted) {
return isClassRefCounted(Node);
}
#if CLANG_VERSION_FULL < 304
/// The 'equalsBoundeNode' matcher was added in clang 3.4.
/// Since infra runs clang 3.3, we polyfill it here.
AST_POLYMORPHIC_MATCHER_P(equalsBoundNode,
std::string, ID) {
BoundNodesTree bindings = Builder->build();
bool haveMatchingResult = false;
struct Visitor : public BoundNodesTree::Visitor {
const NodeType &Node;
std::string ID;
bool &haveMatchingResult;
Visitor(const NodeType &Node, const std::string &ID, bool &haveMatchingResult)
: Node(Node), ID(ID), haveMatchingResult(haveMatchingResult) {}
void visitMatch(const BoundNodes &BoundNodesView) override {
if (BoundNodesView.getNodeAs<NodeType>(ID) == &Node) {
haveMatchingResult = true;
}
}
};
Visitor visitor(Node, ID, haveMatchingResult);
bindings.visitMatches(&visitor);
return haveMatchingResult;
}
#endif
AST_MATCHER(QualType, hasVTable) {
return typeHasVTable(Node);
}
AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) {
return MozChecker::hasCustomAnnotation(&Node, "moz_needs_no_vtable_type");
}
/// This matcher will select classes which are non-memmovable
AST_MATCHER(QualType, isNonMemMovable) {
return isClassNonMemMovable(Node);
}
/// This matcher will select classes which require a memmovable template arg
AST_MATCHER(CXXRecordDecl, needsMemMovable) {
return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_type");
}
}
}
namespace {
void CustomTypeAnnotation::dumpAnnotationReason(DiagnosticsEngine &Diag, QualType T, SourceLocation Loc) {
unsigned InheritsID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "%1 is a %0 type because it inherits from a %0 type %2");
unsigned MemberID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "%1 is a %0 type because member %2 is a %0 type %3");
unsigned ArrayID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "%1 is a %0 type because it is an array of %0 type %2");
unsigned TemplID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "%1 is a %0 type because it has a template argument %0 type %2");
AnnotationReason Reason = directAnnotationReason(T);
for (;;) {
switch (Reason.Kind) {
case RK_ArrayElement:
Diag.Report(Loc, ArrayID)
<< Pretty << T << Reason.Type;
break;
case RK_BaseClass:
{
const CXXRecordDecl *Decl = T->getAsCXXRecordDecl();
assert(Decl && "This type should be a C++ class");
Diag.Report(Decl->getLocation(), InheritsID)
<< Pretty << T << Reason.Type;
break;
}
case RK_Field:
Diag.Report(Reason.Field->getLocation(), MemberID)
<< Pretty << T << Reason.Field << Reason.Type;
break;
default:
return;
}
T = Reason.Type;
Reason = directAnnotationReason(T);
}
}
bool CustomTypeAnnotation::hasLiteralAnnotation(QualType T) const {
#if CLANG_VERSION_FULL >= 306
if (const TagDecl *D = T->getAsTagDecl()) {
#else
if (const CXXRecordDecl *D = T->getAsCXXRecordDecl()) {
#endif
return MozChecker::hasCustomAnnotation(D, Spelling);
}
return false;
}
CustomTypeAnnotation::AnnotationReason CustomTypeAnnotation::directAnnotationReason(QualType T) {
if (hasLiteralAnnotation(T)) {
AnnotationReason Reason = { T, RK_Direct, nullptr };
return Reason;
}
// Check if we have a cached answer
void *Key = T.getAsOpaquePtr();
ReasonCache::iterator Cached = Cache.find(T.getAsOpaquePtr());
if (Cached != Cache.end()) {
return Cached->second;
}
// Check if we have a type which we can recurse into
if (const ArrayType *Array = T->getAsArrayTypeUnsafe()) {
if (hasEffectiveAnnotation(Array->getElementType())) {
AnnotationReason Reason = { Array->getElementType(), RK_ArrayElement, nullptr };
Cache[Key] = Reason;
return Reason;
}
}
// Recurse into base classes
if (const CXXRecordDecl *Decl = T->getAsCXXRecordDecl()) {
if (Decl->hasDefinition()) {
Decl = Decl->getDefinition();
for (const CXXBaseSpecifier &Base : Decl->bases()) {
if (hasEffectiveAnnotation(Base.getType())) {
AnnotationReason Reason = { Base.getType(), RK_BaseClass, nullptr };
Cache[Key] = Reason;
return Reason;
}
}
// Recurse into members
for (const FieldDecl *Field : Decl->fields()) {
if (hasEffectiveAnnotation(Field->getType())) {
AnnotationReason Reason = { Field->getType(), RK_Field, Field };
Cache[Key] = Reason;
return Reason;
}
}
}
}
AnnotationReason Reason = { QualType(), RK_None, nullptr };
Cache[Key] = Reason;
return Reason;
}
bool isPlacementNew(const CXXNewExpr *expr) {
// Regular new expressions aren't placement new
if (expr->getNumPlacementArgs() == 0)
return false;
if (MozChecker::hasCustomAnnotation(expr->getOperatorNew(),
"moz_heap_allocator"))
return false;
return true;
}
DiagnosticsMatcher::DiagnosticsMatcher()
: stackClassChecker(ScopeChecker::eLocal),
globalClassChecker(ScopeChecker::eGlobal)
{
// Stack class assertion: non-local variables of a stack class are forbidden
// (non-localness checked in the callback)
astMatcher.addMatcher(varDecl(hasType(stackClassAggregate())).bind("node"),
&stackClassChecker);
// Stack class assertion: new stack class is forbidden (unless placement new)
astMatcher.addMatcher(newExpr(hasType(pointerType(
pointee(stackClassAggregate())
))).bind("node"), &stackClassChecker);
// Global class assertion: non-global variables of a global class are forbidden
// (globalness checked in the callback)
astMatcher.addMatcher(varDecl(hasType(globalClassAggregate())).bind("node"),
&globalClassChecker);
// Global class assertion: new global class is forbidden
astMatcher.addMatcher(newExpr(hasType(pointerType(
pointee(globalClassAggregate())
))).bind("node"), &globalClassChecker);
// Non-heap class assertion: new non-heap class is forbidden (unless placement
// new)
astMatcher.addMatcher(newExpr(hasType(pointerType(
pointee(nonheapClassAggregate())
))).bind("node"), &nonheapClassChecker);
// Any heap allocation function that returns a non-heap or a stack class or
// a global class is definitely doing something wrong
astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(),
returns(pointerType(pointee(nonheapClassAggregate()))))))).bind("node"),
&nonheapClassChecker);
astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(),
returns(pointerType(pointee(stackClassAggregate()))))))).bind("node"),
&stackClassChecker);
astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(),
returns(pointerType(pointee(globalClassAggregate()))))))).bind("node"),
&globalClassChecker);
astMatcher.addMatcher(callExpr(allOf(hasDeclaration(noArithmeticExprInArgs()),
anyOf(
hasDescendant(binaryOperator(allOf(binaryArithmeticOperator(),
hasLHS(hasDescendant(declRefExpr())),
hasRHS(hasDescendant(declRefExpr()))
)).bind("node")),
hasDescendant(unaryOperator(allOf(unaryArithmeticOperator(),
hasUnaryOperand(allOf(hasType(builtinType()),
anyOf(hasDescendant(declRefExpr()), declRefExpr())))
)).bind("node"))
)
)).bind("call"),
&arithmeticArgChecker);
astMatcher.addMatcher(constructExpr(allOf(hasDeclaration(noArithmeticExprInArgs()),
anyOf(
hasDescendant(binaryOperator(allOf(binaryArithmeticOperator(),
hasLHS(hasDescendant(declRefExpr())),
hasRHS(hasDescendant(declRefExpr()))
)).bind("node")),
hasDescendant(unaryOperator(allOf(unaryArithmeticOperator(),
hasUnaryOperand(allOf(hasType(builtinType()),
anyOf(hasDescendant(declRefExpr()), declRefExpr())))
)).bind("node"))
)
)).bind("call"),
&arithmeticArgChecker);
astMatcher.addMatcher(recordDecl(hasTrivialCtorDtor()).bind("node"),
&trivialCtorDtorChecker);
astMatcher.addMatcher(binaryOperator(allOf(binaryEqualityOperator(),
hasLHS(has(declRefExpr(hasType(qualType((isFloat())))).bind("lhs"))),
hasRHS(has(declRefExpr(hasType(qualType((isFloat())))).bind("rhs"))),
unless(anyOf(isInSystemHeader(), isInSkScalarDotH()))
)).bind("node"),
&nanExprChecker);
// First, look for direct parents of the MemberExpr.
astMatcher.addMatcher(callExpr(callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")),
hasParent(memberExpr(isAddRefOrRelease(),
hasParent(callExpr())).bind("member")
)).bind("node"),
&noAddRefReleaseOnReturnChecker);
// Then, look for MemberExpr that need to be casted to the right type using
// an intermediary CastExpr before we get to the CallExpr.
astMatcher.addMatcher(callExpr(callee(functionDecl(hasNoAddRefReleaseOnReturnAttr()).bind("func")),
hasParent(castExpr(hasParent(memberExpr(isAddRefOrRelease(),
hasParent(callExpr())).bind("member"))))
).bind("node"),
&noAddRefReleaseOnReturnChecker);
// Match declrefs with type "pointer to object of ref-counted type" inside a
// lambda, where the declaration they reference is not inside the lambda.
// This excludes arguments and local variables, leaving only captured
// variables.
astMatcher.addMatcher(lambdaExpr(
hasDescendant(declRefExpr(hasType(pointerType(pointee(isRefCounted()))),
to(decl().bind("decl"))).bind("declref")),
unless(hasDescendant(decl(equalsBoundNode("decl"))))
),
&refCountedInsideLambdaChecker);
// Older clang versions such as the ones used on the infra recognize these
// conversions as 'operator _Bool', but newer clang versions recognize these
// as 'operator bool'.
astMatcher.addMatcher(methodDecl(anyOf(hasName("operator bool"),
hasName("operator _Bool"))).bind("node"),
&explicitOperatorBoolChecker);
astMatcher.addMatcher(classTemplateSpecializationDecl(
allOf(hasAnyTemplateArgument(refersToType(hasVTable())),
hasNeedsNoVTableTypeAttr())).bind("node"),
&needsNoVTableTypeChecker);
// Handle non-mem-movable template specializations
astMatcher.addMatcher(classTemplateSpecializationDecl(
allOf(needsMemMovable(),
hasAnyTemplateArgument(refersToType(isNonMemMovable())))
).bind("specialization"),
&nonMemMovableChecker);
}
void DiagnosticsMatcher::ScopeChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "variable of type %0 only valid on the stack");
unsigned globalID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "variable of type %0 only valid as global");
SourceLocation Loc;
QualType T;
if (const VarDecl *d = Result.Nodes.getNodeAs<VarDecl>("node")) {
if (scope == eLocal) {
// Ignore the match if it's a local variable.
if (d->hasLocalStorage())
return;
} else if (scope == eGlobal) {
// Ignore the match if it's a global variable or a static member of a
// class. The latter is technically not in the global scope, but for the
// use case of classes that intend to avoid introducing static
// initializers that is fine.
if (d->hasGlobalStorage() && !d->isStaticLocal())
return;
}
Loc = d->getLocation();
T = d->getType();
} else if (const CXXNewExpr *expr =
Result.Nodes.getNodeAs<CXXNewExpr>("node")) {
// If it's placement new, then this match doesn't count.
if (scope == eLocal && isPlacementNew(expr))
return;
Loc = expr->getStartLoc();
T = expr->getAllocatedType();
} else if (const CallExpr *expr =
Result.Nodes.getNodeAs<CallExpr>("node")) {
Loc = expr->getLocStart();
T = GetCallReturnType(expr)->getPointeeType();
}
if (scope == eLocal) {
Diag.Report(Loc, stackID) << T;
StackClass.dumpAnnotationReason(Diag, T, Loc);
} else if (scope == eGlobal) {
Diag.Report(Loc, globalID) << T;
GlobalClass.dumpAnnotationReason(Diag, T, Loc);
}
}
void DiagnosticsMatcher::NonHeapClassChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "variable of type %0 is not valid on the heap");
SourceLocation Loc;
QualType T;
if (const CXXNewExpr *expr = Result.Nodes.getNodeAs<CXXNewExpr>("node")) {
// If it's placement new, then this match doesn't count.
if (isPlacementNew(expr))
return;
Loc = expr->getLocStart();
T = expr->getAllocatedType();
} else if (const CallExpr *expr = Result.Nodes.getNodeAs<CallExpr>("node")) {
Loc = expr->getLocStart();
T = GetCallReturnType(expr)->getPointeeType();
}
Diag.Report(Loc, stackID) << T;
NonHeapClass.dumpAnnotationReason(Diag, T, Loc);
}
void DiagnosticsMatcher::ArithmeticArgChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "cannot pass an arithmetic expression of built-in types to %0");
const Expr *expr = Result.Nodes.getNodeAs<Expr>("node");
if (const CallExpr *call = Result.Nodes.getNodeAs<CallExpr>("call")) {
Diag.Report(expr->getLocStart(), errorID) << call->getDirectCallee();
} else if (const CXXConstructExpr *ctr = Result.Nodes.getNodeAs<CXXConstructExpr>("call")) {
Diag.Report(expr->getLocStart(), errorID) << ctr->getConstructor();
}
}
void DiagnosticsMatcher::TrivialCtorDtorChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "class %0 must have trivial constructors and destructors");
const CXXRecordDecl *node = Result.Nodes.getNodeAs<CXXRecordDecl>("node");
bool badCtor = !node->hasTrivialDefaultConstructor();
bool badDtor = !node->hasTrivialDestructor();
if (badCtor || badDtor)
Diag.Report(node->getLocStart(), errorID) << node;
}
void DiagnosticsMatcher::NaNExprChecker::run(
const MatchFinder::MatchResult &Result) {
if (!Result.Context->getLangOpts().CPlusPlus) {
// mozilla::IsNaN is not usable in C, so there is no point in issuing these warnings.
return;
}
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "comparing a floating point value to itself for NaN checking can lead to incorrect results");
unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "consider using mozilla::IsNaN instead");
const BinaryOperator *expr = Result.Nodes.getNodeAs<BinaryOperator>("node");
const DeclRefExpr *lhs = Result.Nodes.getNodeAs<DeclRefExpr>("lhs");
const DeclRefExpr *rhs = Result.Nodes.getNodeAs<DeclRefExpr>("rhs");
const ImplicitCastExpr *lhsExpr = dyn_cast<ImplicitCastExpr>(expr->getLHS());
const ImplicitCastExpr *rhsExpr = dyn_cast<ImplicitCastExpr>(expr->getRHS());
// The AST subtree that we are looking for will look like this:
// -BinaryOperator ==/!=
// |-ImplicitCastExpr LValueToRValue
// | |-DeclRefExpr
// |-ImplicitCastExpr LValueToRValue
// |-DeclRefExpr
// The check below ensures that we are dealing with the correct AST subtree shape, and
// also that both of the found DeclRefExpr's point to the same declaration.
if (lhs->getFoundDecl() == rhs->getFoundDecl() &&
lhsExpr && rhsExpr &&
std::distance(lhsExpr->child_begin(), lhsExpr->child_end()) == 1 &&
std::distance(rhsExpr->child_begin(), rhsExpr->child_end()) == 1 &&
*lhsExpr->child_begin() == lhs &&
*rhsExpr->child_begin() == rhs) {
Diag.Report(expr->getLocStart(), errorID);
Diag.Report(expr->getLocStart(), noteID);
}
}
void DiagnosticsMatcher::NoAddRefReleaseOnReturnChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "%1 cannot be called on the return value of %0");
const Stmt *node = Result.Nodes.getNodeAs<Stmt>("node");
const FunctionDecl *func = Result.Nodes.getNodeAs<FunctionDecl>("func");
const MemberExpr *member = Result.Nodes.getNodeAs<MemberExpr>("member");
const CXXMethodDecl *method = dyn_cast<CXXMethodDecl>(member->getMemberDecl());
Diag.Report(node->getLocStart(), errorID) << func << method;
}
void DiagnosticsMatcher::RefCountedInsideLambdaChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "Refcounted variable %0 of type %1 cannot be captured by a lambda");
unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "Please consider using a smart pointer");
const DeclRefExpr *declref = Result.Nodes.getNodeAs<DeclRefExpr>("declref");
Diag.Report(declref->getLocStart(), errorID) << declref->getFoundDecl() <<
declref->getType()->getPointeeType();
Diag.Report(declref->getLocStart(), noteID);
}
void DiagnosticsMatcher::ExplicitOperatorBoolChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "bad implicit conversion operator for %0");
unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "consider adding the explicit keyword to %0");
const CXXConversionDecl *method = Result.Nodes.getNodeAs<CXXConversionDecl>("node");
const CXXRecordDecl *clazz = method->getParent();
if (!method->isExplicitSpecified() &&
!MozChecker::hasCustomAnnotation(method, "moz_implicit") &&
!IsInSystemHeader(method->getASTContext(), *method) &&
isInterestingDeclForImplicitConversion(method)) {
Diag.Report(method->getLocStart(), errorID) << clazz;
Diag.Report(method->getLocStart(), noteID) << "'operator bool'";
}
}
void DiagnosticsMatcher::NeedsNoVTableTypeChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "%0 cannot be instantiated because %1 has a VTable");
unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "bad instantiation of %0 requested here");
const ClassTemplateSpecializationDecl *specialization =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("node");
// Get the offending template argument
QualType offender;
const TemplateArgumentList &args =
specialization->getTemplateInstantiationArgs();
for (unsigned i = 0; i < args.size(); ++i) {
offender = args[i].getAsType();
if (typeHasVTable(offender)) {
break;
}
}
Diag.Report(specialization->getLocStart(), errorID) << specialization << offender;
Diag.Report(specialization->getPointOfInstantiation(), noteID) << specialization;
}
void DiagnosticsMatcher::NonMemMovableChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error, "Cannot instantiate %0 with non-memmovable template argument %1");
unsigned note1ID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "instantiation of %0 requested here");
unsigned note2ID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Note, "%0 is non-memmovable because of the MOZ_NON_MEMMOVABLE annotation on %1");
unsigned note3ID = Diag.getDiagnosticIDs()->getCustomDiagID(DiagnosticIDs::Note, "%0");
// Get the specialization
const ClassTemplateSpecializationDecl *specialization =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("specialization");
SourceLocation requestLoc = specialization->getPointOfInstantiation();
const CXXRecordDecl *templ =
specialization->getSpecializedTemplate()->getTemplatedDecl();
// Report an error for every template argument which is non-memmovable
const TemplateArgumentList &args =
specialization->getTemplateInstantiationArgs();
for (unsigned i = 0; i < args.size(); ++i) {
QualType argType = args[i].getAsType();
if (isClassNonMemMovable(args[i].getAsType())) {
const CXXRecordDecl *reason = findWhyClassIsNonMemMovable(argType);
Diag.Report(specialization->getLocation(), errorID)
<< specialization << argType;
// XXX It would be really nice if we could get the instantiation stack information
// from Sema such that we could print a full template instantiation stack, however,
// it seems as though that information is thrown out by the time we get here so we
// can only report one level of template specialization (which in many cases won't
// be useful)
Diag.Report(requestLoc, note1ID)
<< specialization;
Diag.Report(reason->getLocation(), note2ID)
<< argType << reason;
}
}
}
class MozCheckAction : public PluginASTAction {
public:
ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, StringRef fileName) override {
#if CLANG_VERSION_FULL >= 306
std::unique_ptr<MozChecker> checker(llvm::make_unique<MozChecker>(CI));
ASTConsumerPtr other(checker->getOtherConsumer());
std::vector<ASTConsumerPtr> consumers;
consumers.push_back(std::move(checker));
consumers.push_back(std::move(other));
return llvm::make_unique<MultiplexConsumer>(std::move(consumers));
#else
MozChecker *checker = new MozChecker(CI);
ASTConsumer *consumers[] = { checker, checker->getOtherConsumer() };
return new MultiplexConsumer(consumers);
#endif
}
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &args) override {
return true;
}
};
}
static FrontendPluginRegistry::Add<MozCheckAction>
X("moz-check", "check moz action");