mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
e3737b1c60
Differential Revision: https://phabricator.services.mozilla.com/D105012
500 lines
15 KiB
C++
500 lines
15 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/. */
|
|
|
|
#ifndef Utils_h__
|
|
#define Utils_h__
|
|
|
|
#include "CustomAttributes.h"
|
|
#include "ThirdPartyPaths.h"
|
|
#include "ThreadAllows.h"
|
|
#include "plugin.h"
|
|
|
|
#if CLANG_VERSION_FULL >= 1300
|
|
// Starting with clang-13 some functions from StringRef have been renamed
|
|
#define compare_lower compare_insensitive
|
|
#endif
|
|
|
|
inline StringRef getFilename(const SourceManager &SM, SourceLocation Loc) {
|
|
// We use the presumed location to handle #line directives and such, so the
|
|
// plugin is friendly to icecc / sccache users.
|
|
auto PL = SM.getPresumedLoc(Loc);
|
|
if (PL.isValid()) {
|
|
return StringRef(PL.getFilename());
|
|
}
|
|
return SM.getFilename(Loc);
|
|
}
|
|
|
|
// Check if the given expression contains an assignment expression.
|
|
// This can either take the form of a Binary Operator or a
|
|
// Overloaded Operator Call.
|
|
inline bool hasSideEffectAssignment(const Expr *Expression) {
|
|
if (auto OpCallExpr = dyn_cast_or_null<CXXOperatorCallExpr>(Expression)) {
|
|
auto BinOp = OpCallExpr->getOperator();
|
|
if (BinOp == OO_Equal || (BinOp >= OO_PlusEqual && BinOp <= OO_PipeEqual)) {
|
|
return true;
|
|
}
|
|
} else if (auto BinOpExpr = dyn_cast_or_null<BinaryOperator>(Expression)) {
|
|
if (BinOpExpr->isAssignmentOp()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Recurse to children.
|
|
for (const Stmt *SubStmt : Expression->children()) {
|
|
auto ChildExpr = dyn_cast_or_null<Expr>(SubStmt);
|
|
if (ChildExpr && hasSideEffectAssignment(ChildExpr)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <class T>
|
|
inline bool ASTIsInSystemHeader(const ASTContext &AC, const T &D) {
|
|
auto &SourceManager = AC.getSourceManager();
|
|
auto ExpansionLoc = SourceManager.getExpansionLoc(D.getBeginLoc());
|
|
if (ExpansionLoc.isInvalid()) {
|
|
return false;
|
|
}
|
|
return SourceManager.isInSystemHeader(ExpansionLoc);
|
|
}
|
|
|
|
template <typename T> inline StringRef getNameChecked(const T &D) {
|
|
return D->getIdentifier() ? D->getName() : "";
|
|
}
|
|
|
|
/// A cached data of whether classes are refcounted or not.
|
|
typedef DenseMap<const CXXRecordDecl *, std::pair<const Decl *, bool>>
|
|
RefCountedMap;
|
|
extern RefCountedMap RefCountedClasses;
|
|
|
|
inline 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 = getNameChecked(Method);
|
|
if (Name == "AddRef") {
|
|
SeenAddRef = true;
|
|
} else if (Name == "Release") {
|
|
SeenRelease = true;
|
|
}
|
|
}
|
|
RefCountedClasses[D] = std::make_pair(D, SeenAddRef && SeenRelease);
|
|
return SeenAddRef && SeenRelease;
|
|
}
|
|
|
|
inline bool isClassRefCounted(QualType T);
|
|
|
|
inline 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;
|
|
}
|
|
|
|
inline bool isClassRefCounted(QualType T) {
|
|
while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe())
|
|
T = ArrTy->getElementType();
|
|
CXXRecordDecl *Clazz = T->getAsCXXRecordDecl();
|
|
return Clazz ? isClassRefCounted(Clazz) : false;
|
|
}
|
|
|
|
inline const FieldDecl *getClassRefCntMember(const CXXRecordDecl *D) {
|
|
for (RecordDecl::field_iterator Field = D->field_begin(), E = D->field_end();
|
|
Field != E; ++Field) {
|
|
if (getNameChecked(Field) == "mRefCnt") {
|
|
return *Field;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline bool typeHasVTable(QualType T) {
|
|
while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe())
|
|
T = ArrTy->getElementType();
|
|
CXXRecordDecl *Offender = T->getAsCXXRecordDecl();
|
|
return Offender && Offender->hasDefinition() && Offender->isDynamicClass();
|
|
}
|
|
|
|
inline StringRef getDeclarationNamespace(const Decl *Declaration) {
|
|
const DeclContext *DC =
|
|
Declaration->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;
|
|
}
|
|
|
|
inline bool isInIgnoredNamespaceForImplicitCtor(const Decl *Declaration) {
|
|
StringRef Name = getDeclarationNamespace(Declaration);
|
|
if (Name == "") {
|
|
return false;
|
|
}
|
|
|
|
return Name == "std" || // standard C++ lib
|
|
Name == "__gnu_cxx" || // gnu C++ lib
|
|
Name == "boost" || // boost
|
|
Name == "webrtc" || // upstream webrtc
|
|
Name == "rtc" || // upstream webrtc 'base' package
|
|
Name.startswith("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
|
|
Name == "Json" || // jsoncpp
|
|
Name == "rlbox" || // rlbox
|
|
Name == "v8"; // irregexp
|
|
}
|
|
|
|
inline bool isInIgnoredNamespaceForImplicitConversion(const Decl *Declaration) {
|
|
StringRef Name = getDeclarationNamespace(Declaration);
|
|
if (Name == "") {
|
|
return false;
|
|
}
|
|
|
|
return Name == "std" || // standard C++ lib
|
|
Name == "__gnu_cxx" || // gnu C++ lib
|
|
Name == "google_breakpad" || // breakpad
|
|
Name == "webrtc" || // libwebrtc
|
|
Name == "testing" || // gtest
|
|
Name == "rlbox"; // rlbox
|
|
}
|
|
|
|
inline bool isIgnoredPathForImplicitConversion(const Decl *Declaration) {
|
|
Declaration = Declaration->getCanonicalDecl();
|
|
SourceLocation Loc = Declaration->getLocation();
|
|
const SourceManager &SM = Declaration->getASTContext().getSourceManager();
|
|
SmallString<1024> FileName = getFilename(SM, 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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
inline bool isIgnoredPathForSprintfLiteral(const CallExpr *Call,
|
|
const SourceManager &SM) {
|
|
SourceLocation Loc = Call->getBeginLoc();
|
|
SmallString<1024> FileName = getFilename(SM, 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("angle")) == 0 ||
|
|
Begin->compare_lower(StringRef("chromium")) == 0 ||
|
|
Begin->compare_lower(StringRef("crashreporter")) == 0 ||
|
|
Begin->compare_lower(StringRef("google-breakpad")) == 0 ||
|
|
Begin->compare_lower(StringRef("gflags")) == 0 ||
|
|
Begin->compare_lower(StringRef("harfbuzz")) == 0 ||
|
|
Begin->compare_lower(StringRef("icu")) == 0 ||
|
|
Begin->compare_lower(StringRef("jsoncpp")) == 0 ||
|
|
Begin->compare_lower(StringRef("libstagefright")) == 0 ||
|
|
Begin->compare_lower(StringRef("transport")) == 0 ||
|
|
Begin->compare_lower(StringRef("protobuf")) == 0 ||
|
|
Begin->compare_lower(StringRef("skia")) == 0 ||
|
|
Begin->compare_lower(StringRef("sfntly")) == 0 ||
|
|
// Gtest uses snprintf as GTEST_SNPRINTF_ with sizeof
|
|
Begin->compare_lower(StringRef("testing")) == 0) {
|
|
return true;
|
|
}
|
|
if (Begin->compare_lower(StringRef("webrtc")) == 0) {
|
|
// Ignore trunk/webrtc, but not media/webrtc
|
|
++Begin;
|
|
return Begin != End && Begin->compare_lower(StringRef("trunk")) == 0;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool isInterestingDeclForImplicitConversion(const Decl *Declaration) {
|
|
return !isInIgnoredNamespaceForImplicitConversion(Declaration) &&
|
|
!isIgnoredPathForImplicitConversion(Declaration);
|
|
}
|
|
|
|
inline bool isIgnoredExprForMustUse(const Expr *E) {
|
|
if (const CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(E)) {
|
|
switch (OpCall->getOperator()) {
|
|
case OO_Equal:
|
|
case OO_PlusEqual:
|
|
case OO_MinusEqual:
|
|
case OO_StarEqual:
|
|
case OO_SlashEqual:
|
|
case OO_PercentEqual:
|
|
case OO_CaretEqual:
|
|
case OO_AmpEqual:
|
|
case OO_PipeEqual:
|
|
case OO_LessLessEqual:
|
|
case OO_GreaterGreaterEqual:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (const BinaryOperator *Op = dyn_cast<BinaryOperator>(E)) {
|
|
return Op->isAssignmentOp();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool typeIsRefPtr(QualType Q) {
|
|
CXXRecordDecl *D = Q->getAsCXXRecordDecl();
|
|
if (!D || !D->getIdentifier()) {
|
|
return false;
|
|
}
|
|
|
|
StringRef name = D->getName();
|
|
if (name == "RefPtr" || name == "nsCOMPtr") {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// The method defined in clang for ignoring implicit nodes doesn't work with
|
|
// some AST trees. To get around this, we define our own implementation of
|
|
// IgnoreTrivials.
|
|
inline const Stmt *MaybeSkipOneTrivial(const Stmt *s) {
|
|
if (!s) {
|
|
return nullptr;
|
|
}
|
|
if (auto *ewc = dyn_cast<ExprWithCleanups>(s)) {
|
|
return ewc->getSubExpr();
|
|
}
|
|
if (auto *mte = dyn_cast<MaterializeTemporaryExpr>(s)) {
|
|
// With clang 10 and up `getTemporary` has been replaced with the more
|
|
// versatile `getSubExpr`.
|
|
#if CLANG_VERSION_FULL >= 1000
|
|
return mte->getSubExpr();
|
|
#else
|
|
return mte->GetTemporaryExpr();
|
|
#endif
|
|
}
|
|
if (auto *bte = dyn_cast<CXXBindTemporaryExpr>(s)) {
|
|
return bte->getSubExpr();
|
|
}
|
|
if (auto *ce = dyn_cast<CastExpr>(s)) {
|
|
s = ce->getSubExpr();
|
|
}
|
|
if (auto *pe = dyn_cast<ParenExpr>(s)) {
|
|
s = pe->getSubExpr();
|
|
}
|
|
// Not a trivial.
|
|
return s;
|
|
}
|
|
|
|
inline const Stmt *IgnoreTrivials(const Stmt *s) {
|
|
while (true) {
|
|
const Stmt *newS = MaybeSkipOneTrivial(s);
|
|
if (newS == s) {
|
|
return newS;
|
|
}
|
|
s = newS;
|
|
}
|
|
|
|
// Unreachable
|
|
return nullptr;
|
|
}
|
|
|
|
inline const Expr *IgnoreTrivials(const Expr *e) {
|
|
return cast_or_null<Expr>(IgnoreTrivials(static_cast<const Stmt *>(e)));
|
|
}
|
|
|
|
// Returns the input if the input is not a trivial.
|
|
inline const Expr *MaybeSkipOneTrivial(const Expr *e) {
|
|
return cast_or_null<Expr>(MaybeSkipOneTrivial(static_cast<const Stmt *>(e)));
|
|
}
|
|
|
|
const FieldDecl *getBaseRefCntMember(QualType T);
|
|
|
|
inline const FieldDecl *getBaseRefCntMember(const CXXRecordDecl *D) {
|
|
const FieldDecl *RefCntMember = getClassRefCntMember(D);
|
|
if (RefCntMember && isClassRefCounted(D)) {
|
|
return RefCntMember;
|
|
}
|
|
|
|
for (CXXRecordDecl::base_class_const_iterator Base = D->bases_begin(),
|
|
E = D->bases_end();
|
|
Base != E; ++Base) {
|
|
RefCntMember = getBaseRefCntMember(Base->getType());
|
|
if (RefCntMember) {
|
|
return RefCntMember;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline const FieldDecl *getBaseRefCntMember(QualType T) {
|
|
while (const clang::ArrayType *ArrTy = T->getAsArrayTypeUnsafe())
|
|
T = ArrTy->getElementType();
|
|
CXXRecordDecl *Clazz = T->getAsCXXRecordDecl();
|
|
return Clazz ? getBaseRefCntMember(Clazz) : 0;
|
|
}
|
|
|
|
inline bool isPlacementNew(const CXXNewExpr *Expression) {
|
|
// Regular new expressions aren't placement new
|
|
if (Expression->getNumPlacementArgs() == 0)
|
|
return false;
|
|
const FunctionDecl *Declaration = Expression->getOperatorNew();
|
|
if (Declaration && hasCustomAttribute<moz_heap_allocator>(Declaration)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
extern DenseMap<StringRef, bool> InThirdPartyPathCache;
|
|
|
|
inline bool inThirdPartyPath(SourceLocation Loc, const SourceManager &SM) {
|
|
StringRef OriginalFileName = getFilename(SM, Loc);
|
|
auto pair = InThirdPartyPathCache.find(OriginalFileName);
|
|
if (pair != InThirdPartyPathCache.end()) {
|
|
return pair->second;
|
|
}
|
|
|
|
SmallString<1024> FileName = OriginalFileName;
|
|
llvm::sys::fs::make_absolute(FileName);
|
|
|
|
for (uint32_t i = 0; i < MOZ_THIRD_PARTY_PATHS_COUNT; ++i) {
|
|
auto PathB = sys::path::begin(FileName);
|
|
auto PathE = sys::path::end(FileName);
|
|
|
|
auto ThirdPartyB = sys::path::begin(MOZ_THIRD_PARTY_PATHS[i]);
|
|
auto ThirdPartyE = sys::path::end(MOZ_THIRD_PARTY_PATHS[i]);
|
|
|
|
for (; PathB != PathE; ++PathB) {
|
|
// Perform an inner loop to compare path segments, checking if the current
|
|
// segment is the start of the current third party path.
|
|
auto IPathB = PathB;
|
|
auto IThirdPartyB = ThirdPartyB;
|
|
for (; IPathB != PathE && IThirdPartyB != ThirdPartyE;
|
|
++IPathB, ++IThirdPartyB) {
|
|
if (IPathB->compare_lower(*IThirdPartyB) != 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We found a match!
|
|
if (IThirdPartyB == ThirdPartyE) {
|
|
InThirdPartyPathCache.insert(std::make_pair(OriginalFileName, true));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
InThirdPartyPathCache.insert(std::make_pair(OriginalFileName, false));
|
|
return false;
|
|
}
|
|
|
|
inline bool inThirdPartyPath(const Decl *D, ASTContext *context) {
|
|
D = D->getCanonicalDecl();
|
|
SourceLocation Loc = D->getLocation();
|
|
const SourceManager &SM = context->getSourceManager();
|
|
|
|
return inThirdPartyPath(Loc, SM);
|
|
}
|
|
|
|
inline CXXRecordDecl *getNonTemplateSpecializedCXXRecordDecl(QualType Q) {
|
|
auto *D = Q->getAsCXXRecordDecl();
|
|
|
|
if (!D) {
|
|
auto TemplateQ = Q->getAs<TemplateSpecializationType>();
|
|
if (!TemplateQ) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto TemplateDecl = TemplateQ->getTemplateName().getAsTemplateDecl();
|
|
if (!TemplateDecl) {
|
|
return nullptr;
|
|
}
|
|
|
|
D = dyn_cast_or_null<CXXRecordDecl>(TemplateDecl->getTemplatedDecl());
|
|
if (!D) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return D;
|
|
}
|
|
|
|
inline bool inThirdPartyPath(const Decl *D) {
|
|
return inThirdPartyPath(D, &D->getASTContext());
|
|
}
|
|
|
|
inline bool inThirdPartyPath(const Stmt *S, ASTContext *context) {
|
|
SourceLocation Loc = S->getBeginLoc();
|
|
const SourceManager &SM = context->getSourceManager();
|
|
auto ExpansionLoc = SM.getExpansionLoc(Loc);
|
|
if (ExpansionLoc.isInvalid()) {
|
|
return inThirdPartyPath(Loc, SM);
|
|
}
|
|
return inThirdPartyPath(ExpansionLoc, SM);
|
|
}
|
|
|
|
/// Polyfill for CXXOperatorCallExpr::isInfixBinaryOp()
|
|
inline bool isInfixBinaryOp(const CXXOperatorCallExpr *OpCall) {
|
|
#if CLANG_VERSION_FULL >= 400
|
|
return OpCall->isInfixBinaryOp();
|
|
#else
|
|
// Taken from clang source.
|
|
if (OpCall->getNumArgs() != 2)
|
|
return false;
|
|
|
|
switch (OpCall->getOperator()) {
|
|
case OO_Call:
|
|
case OO_Subscript:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#undef compare_lower
|
|
#endif
|