diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index 1a9d6d3127fb7f..774410d1c08d51 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -12,6 +12,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/ParentMapContext.h" #include namespace clang { @@ -27,6 +28,13 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) { E = tempExpr->getSubExpr(); continue; } + if (auto *DRE = dyn_cast(E)) { + auto *decl = DRE->getFoundDecl(); + if (auto *VD = dyn_cast(decl)) { + if (isTypeRefCounted(VD->getType())) + return {E, true}; + } + } if (auto *cast = dyn_cast(E)) { if (StopAtFirstRefCountedObj) { if (auto *ConversionFunc = @@ -95,11 +103,136 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) { return {E, false}; } +bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded, + const VarDecl *MaybeGuardian) { + assert(Guarded); + assert(MaybeGuardian); + + if (!MaybeGuardian->isLocalVarDecl()) + return false; + + const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr; + + ASTContext &ctx = MaybeGuardian->getASTContext(); + + for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian); + !guardianAncestors.empty(); + guardianAncestors = ctx.getParents( + *guardianAncestors + .begin()) // FIXME - should we handle all of the parents? + ) { + for (auto &guardianAncestor : guardianAncestors) { + if (auto *CStmtParentAncestor = guardianAncestor.get()) { + guardiansClosestCompStmtAncestor = CStmtParentAncestor; + break; + } + } + if (guardiansClosestCompStmtAncestor) + break; + } + + if (!guardiansClosestCompStmtAncestor) + return false; + + // We need to skip the first CompoundStmt to avoid situation when guardian is + // defined in the same scope as guarded variable. + bool HaveSkippedFirstCompoundStmt = false; + for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded); + !guardedVarAncestors.empty(); + guardedVarAncestors = ctx.getParents( + *guardedVarAncestors + .begin()) // FIXME - should we handle all of the parents? + ) { + for (auto &guardedVarAncestor : guardedVarAncestors) { + if (guardedVarAncestor.get()) { + if (!HaveSkippedFirstCompoundStmt) + HaveSkippedFirstCompoundStmt = true; + continue; + } + if (guardedVarAncestor.get()) { + if (!HaveSkippedFirstCompoundStmt) + HaveSkippedFirstCompoundStmt = true; + continue; + } + if (guardedVarAncestor.get()) { + if (!HaveSkippedFirstCompoundStmt) + HaveSkippedFirstCompoundStmt = true; + continue; + } + if (auto *CStmtAncestor = guardedVarAncestor.get()) { + if (!HaveSkippedFirstCompoundStmt) { + HaveSkippedFirstCompoundStmt = true; + continue; + } + if (CStmtAncestor == guardiansClosestCompStmtAncestor) + return true; + } + } + } + + return false; +} + +// FIXME: should be defined by annotations in the future +bool isRefcountedStringsHack(const VarDecl *V) { + assert(V); + auto safeClass = [](const std::string &className) { + return className == "String" || className == "AtomString" || + className == "UniquedString" || className == "Identifier"; + }; + QualType QT = V->getType(); + auto *T = QT.getTypePtr(); + if (auto *CXXRD = T->getAsCXXRecordDecl()) { + if (safeClass(safeGetName(CXXRD))) + return true; + } + if (T->isPointerType() || T->isReferenceType()) { + if (auto *CXXRD = T->getPointeeCXXRecordDecl()) { + if (safeClass(safeGetName(CXXRD))) + return true; + } + } + return false; +} + +bool isVarDeclGuardedInit(const VarDecl *V, const Expr *InitE) { + // "this" parameter like any other argument is considered safe. + if (isa(InitE)) + return true; + + if (auto *Ref = llvm::dyn_cast(InitE)) { + if (auto *MaybeGuardian = dyn_cast_or_null(Ref->getFoundDecl())) { + // Parameters are guaranteed to be safe for the duration of the call. + if (isa(MaybeGuardian)) + return true; + + const auto *MaybeGuardianArgType = MaybeGuardian->getType().getTypePtr(); + if (!MaybeGuardianArgType) + return false; + + const CXXRecordDecl *const MaybeGuardianArgCXXRecord = + MaybeGuardianArgType->getAsCXXRecordDecl(); + + if (!MaybeGuardianArgCXXRecord) + return false; + + if (MaybeGuardian->isLocalVarDecl() && + (isRefCounted(MaybeGuardianArgCXXRecord) || + isRefcountedStringsHack(MaybeGuardian)) && + isGuardedScopeEmbeddedInGuardianScope(V, MaybeGuardian)) { + return true; + } + } + } + + return false; +} + bool isASafeCallArg(const Expr *E) { assert(E); if (auto *Ref = dyn_cast(E)) { if (auto *D = dyn_cast_or_null(Ref->getFoundDecl())) { - if (isa(D) || D->isLocalVarDecl()) + if (isa(D)) return true; } } diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h index e35ea4ef05dd17..bdbe295df6ea39 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h @@ -53,6 +53,11 @@ class Expr; std::pair tryToFindPtrOrigin(const clang::Expr *E, bool StopAtFirstRefCountedObj); +// Returns true if \P V is a variable declaration and \p E is its +// initialization expression, and there is a "guardian" RefPtr or RefPtr +// protecting the same value. +bool isVarDeclGuardedInit(const VarDecl *V, const clang::Expr *InitE); + /// For \p E referring to a ref-countable/-counted pointer/reference we return /// whether it's a safe call argument. Examples: function parameter or /// this-pointer. The logic relies on the set of recursive rules we enforce for diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index a7891d2da07c18..58212625349889 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -122,12 +122,21 @@ bool isCtorOfRefCounted(const clang::FunctionDecl *F) { bool isReturnValueRefCounted(const clang::FunctionDecl *F) { assert(F); - QualType type = F->getReturnType(); + return isTypeRefCounted(F->getReturnType()); +} + +bool isTypeRefCounted(QualType type) { while (!type.isNull()) { if (auto *elaboratedT = type->getAs()) { type = elaboratedT->desugar(); continue; } + if (auto *deduced = type->getAs()) { + if (auto *decl = deduced->getTemplateName().getAsTemplateDecl()) { + auto name = decl->getNameAsString(); + return name == "Ref" || name == "RefPtr"; + } + } if (auto *specialT = type->getAs()) { if (auto *decl = specialT->getTemplateName().getAsTemplateDecl()) { auto name = decl->getNameAsString(); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index e07cd31395747d..5051f4e321bb6d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -9,7 +9,7 @@ #ifndef LLVM_CLANG_ANALYZER_WEBKIT_PTRTYPESEMANTICS_H #define LLVM_CLANG_ANALYZER_WEBKIT_PTRTYPESEMANTICS_H -#include "llvm/ADT/APInt.h" +#include "clang/AST/Type.h" #include "llvm/ADT/DenseMap.h" #include @@ -55,6 +55,9 @@ bool isCtorOfRefCounted(const clang::FunctionDecl *F); /// \returns true if \p F returns a ref-counted object, false if not. bool isReturnValueRefCounted(const clang::FunctionDecl *F); +/// \returns true if \p type is ref-counted object, false if not. +bool isTypeRefCounted(QualType type); + /// \returns true if \p M is getter of a ref-counted class, false if not. std::optional isGetterOfRefCounted(const clang::CXXMethodDecl* Method); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp index 8b41a949fd6734..a9e55d068341c3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp @@ -137,6 +137,16 @@ class UncountedCallArgsChecker return true; } + if (auto *DRE = dyn_cast(ArgOrigin.first)) { + auto *Decl = DRE->getFoundDecl(); + if (auto *VD = dyn_cast(Decl)) { + std::pair ArgOrigin = + tryToFindPtrOrigin(VD->getInit(), false); + if (ArgOrigin.first && isVarDeclGuardedInit(VD, ArgOrigin.first)) + return true; + } + } + return isASafeCallArg(ArgOrigin.first); } diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp index 5a72f53b12edaa..9b976c0947edcd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp @@ -48,83 +48,6 @@ bool isDeclaredInForOrIf(const VarDecl *Var) { return false; } -// FIXME: should be defined by anotations in the future -bool isRefcountedStringsHack(const VarDecl *V) { - assert(V); - auto safeClass = [](const std::string &className) { - return className == "String" || className == "AtomString" || - className == "UniquedString" || className == "Identifier"; - }; - QualType QT = V->getType(); - auto *T = QT.getTypePtr(); - if (auto *CXXRD = T->getAsCXXRecordDecl()) { - if (safeClass(safeGetName(CXXRD))) - return true; - } - if (T->isPointerType() || T->isReferenceType()) { - if (auto *CXXRD = T->getPointeeCXXRecordDecl()) { - if (safeClass(safeGetName(CXXRD))) - return true; - } - } - return false; -} - -bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded, - const VarDecl *MaybeGuardian) { - assert(Guarded); - assert(MaybeGuardian); - - if (!MaybeGuardian->isLocalVarDecl()) - return false; - - const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr; - - ASTContext &ctx = MaybeGuardian->getASTContext(); - - for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian); - !guardianAncestors.empty(); - guardianAncestors = ctx.getParents( - *guardianAncestors - .begin()) // FIXME - should we handle all of the parents? - ) { - for (auto &guardianAncestor : guardianAncestors) { - if (auto *CStmtParentAncestor = guardianAncestor.get()) { - guardiansClosestCompStmtAncestor = CStmtParentAncestor; - break; - } - } - if (guardiansClosestCompStmtAncestor) - break; - } - - if (!guardiansClosestCompStmtAncestor) - return false; - - // We need to skip the first CompoundStmt to avoid situation when guardian is - // defined in the same scope as guarded variable. - bool HaveSkippedFirstCompoundStmt = false; - for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded); - !guardedVarAncestors.empty(); - guardedVarAncestors = ctx.getParents( - *guardedVarAncestors - .begin()) // FIXME - should we handle all of the parents? - ) { - for (auto &guardedVarAncestor : guardedVarAncestors) { - if (auto *CStmtAncestor = guardedVarAncestor.get()) { - if (!HaveSkippedFirstCompoundStmt) { - HaveSkippedFirstCompoundStmt = true; - continue; - } - if (CStmtAncestor == guardiansClosestCompStmtAncestor) - return true; - } - } - } - - return false; -} - class UncountedLocalVarsChecker : public Checker> { BugType Bug{this, @@ -181,35 +104,9 @@ class UncountedLocalVarsChecker if (!InitArgOrigin) return; - if (isa(InitArgOrigin)) + if (isVarDeclGuardedInit(V, InitArgOrigin)) return; - if (auto *Ref = llvm::dyn_cast(InitArgOrigin)) { - if (auto *MaybeGuardian = - dyn_cast_or_null(Ref->getFoundDecl())) { - const auto *MaybeGuardianArgType = - MaybeGuardian->getType().getTypePtr(); - if (!MaybeGuardianArgType) - return; - const CXXRecordDecl *const MaybeGuardianArgCXXRecord = - MaybeGuardianArgType->getAsCXXRecordDecl(); - if (!MaybeGuardianArgCXXRecord) - return; - - if (MaybeGuardian->isLocalVarDecl() && - (isRefCounted(MaybeGuardianArgCXXRecord) || - isRefcountedStringsHack(MaybeGuardian)) && - isGuardedScopeEmbeddedInGuardianScope(V, MaybeGuardian)) { - return; - } - - // Parameters are guaranteed to be safe for the duration of the call - // by another checker. - if (isa(MaybeGuardian)) - return; - } - } - reportBug(V); } } diff --git a/clang/test/Analysis/Checkers/WebKit/guarded-uncounted-obj-arg.cpp b/clang/test/Analysis/Checkers/WebKit/guarded-uncounted-obj-arg.cpp new file mode 100644 index 00000000000000..63611bace72d1f --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/guarded-uncounted-obj-arg.cpp @@ -0,0 +1,65 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s + +#include "mock-types.h" + +class Number { +public: + Number(); + ~Number(); +}; + +int someFunction(); + +class RefCounted { +public: + void ref() const; + void deref() const; + void someMethod(); + bool isDerived() const { return false; } +}; + +class Derived : public RefCounted { +public: + void otherMethod(); + bool isDerived() const { return true; } +}; + +template +struct TypeCastTraits { + static bool isOfType(const S &arg) { return arg.isDerived(); } +}; + +RefCounted *object(); +void someFunction(const RefCounted&); + +void test() { + RefPtr obj = object(); + + if (!obj->isDerived()) { + auto* base = obj.get(); + base->someMethod(); + } + + if (auto *derived = dynamicDowncast(*obj)) + derived->otherMethod(); + + while (auto *derived = dynamicDowncast(*obj)) { + derived->otherMethod(); + break; + } + + for (auto *derived = dynamicDowncast(*obj); true; ) { + derived->otherMethod(); + break; + } + + do { + auto *derived = dynamicDowncast(*obj); + derived->otherMethod(); + } while (0); + + auto* derived = dynamicDowncast(*obj); + derived->otherMethod(); + // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}} +} + diff --git a/clang/test/Analysis/Checkers/WebKit/mock-types.h b/clang/test/Analysis/Checkers/WebKit/mock-types.h index 82db67bb031dd6..82d78d8a20f0e1 100644 --- a/clang/test/Analysis/Checkers/WebKit/mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/mock-types.h @@ -63,6 +63,28 @@ struct RefCountable { void deref() {} }; -template T *downcast(T *t) { return t; } +template +struct TypeCastTraits { + static bool isOfType(S&); +}; + +// Type checking function, to use before casting with downcast<>(). +template +inline bool is(const S &source) { + return TypeCastTraits::isOfType(source); +} + +template +inline bool is(S *source) { + return source && TypeCastTraits::isOfType(*source); +} + +template T *downcast(S *t) { return static_cast(t); } +template T *dynamicDowncast(S &t) { + return is(t) ? &static_cast(t) : nullptr; +} +template T *dynamicDowncast(S *t) { + return is(t) ? static_cast(t) : nullptr; +} #endif