Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h +++ /dev/null @@ -1,55 +0,0 @@ -//===- DynamicCastInfo.h - Runtime cast information -------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H -#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H - -#include "clang/AST/Type.h" - -namespace clang { -namespace ento { - -class DynamicCastInfo { -public: - enum CastResult { Success, Failure }; - - DynamicCastInfo(QualType from, QualType to, CastResult resultKind) - : From(from), To(to), ResultKind(resultKind) {} - - QualType from() const { return From; } - QualType to() const { return To; } - - bool equals(QualType from, QualType to) const { - return From == from && To == to; - } - - bool succeeds() const { return ResultKind == CastResult::Success; } - bool fails() const { return ResultKind == CastResult::Failure; } - - bool operator==(const DynamicCastInfo &RHS) const { - return From == RHS.From && To == RHS.To; - } - bool operator<(const DynamicCastInfo &RHS) const { - return From < RHS.From && To < RHS.To; - } - - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.Add(From); - ID.Add(To); - ID.AddInteger(ResultKind); - } - -private: - QualType From, To; - CastResult ResultKind; -}; - -} // namespace ento -} // namespace clang - -#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h @@ -16,7 +16,6 @@ #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPE_H #include "clang/AST/Type.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -37,10 +36,9 @@ const MemRegion *MR); /// Get dynamic cast information from \p CastFromTy to \p CastToTy of \p MR. -const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, - const MemRegion *MR, - QualType CastFromTy, - QualType CastToTy); +const DynamicTypeInfo *getFailedCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastToTy); /// Set dynamic type information of the region; return the new state. ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, @@ -53,9 +51,9 @@ /// Set dynamic type and cast information of the region; return the new state. ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, const MemRegion *MR, - QualType CastFromTy, QualType CastToTy, - bool IsCastSucceeds); + const DynamicTypeInfo *CastInfo, + bool CastSucceeds); /// Removes the dead type informations from \p State. ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR); Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h @@ -18,32 +18,33 @@ /// of a region in a given state along the analysis path. class DynamicTypeInfo { public: - DynamicTypeInfo() : DynTy(QualType()) {} + DynamicTypeInfo() : Ty(QualType()) {} - DynamicTypeInfo(QualType Ty, bool CanBeSub = true) - : DynTy(Ty), CanBeASubClass(CanBeSub) {} + DynamicTypeInfo(QualType ty, bool CanBeSub = true) + : Ty(ty), CanBeASubClass(CanBeSub) {} /// Returns false if the type information is precise (the type 'DynTy' is /// the only type in the lattice), true otherwise. bool canBeASubClass() const { return CanBeASubClass; } /// Returns true if the dynamic type info is available. - bool isValid() const { return !DynTy.isNull(); } + bool isValid() const { return !Ty.isNull(); } /// Returns the currently inferred upper bound on the runtime type. - QualType getType() const { return DynTy; } + QualType getType() const { return Ty; } bool operator==(const DynamicTypeInfo &RHS) const { - return DynTy == RHS.DynTy && CanBeASubClass == RHS.CanBeASubClass; + return Ty == RHS.Ty && CanBeASubClass == RHS.CanBeASubClass; } + bool operator<(const DynamicTypeInfo &RHS) const { return Ty < RHS.Ty; } void Profile(llvm::FoldingSetNodeID &ID) const { - ID.Add(DynTy); + ID.Add(Ty); ID.AddBoolean(CanBeASubClass); } private: - QualType DynTy; + QualType Ty; bool CanBeASubClass; }; Index: clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -8,12 +8,6 @@ // // This defines CastValueChecker which models casts of custom RTTIs. // -// TODO list: -// - It only allows one succesful cast between two types however in the wild -// the object could be casted to multiple types. -// - It needs to check the most likely type information from the dynamic type -// map to increase precision of dynamic casting. -// //===----------------------------------------------------------------------===// #include "clang/AST/DeclTemplate.h" @@ -92,21 +86,103 @@ }; } // namespace -static bool isInfeasibleCast(const DynamicCastInfo *CastInfo, +static bool isDerivedFrom(QualType X, QualType Y) { + const CXXRecordDecl *XRD = X->isPointerType() ? X->getPointeeCXXRecordDecl() + : X->getAsCXXRecordDecl(); + const CXXRecordDecl *YRD = Y->isPointerType() ? Y->getPointeeCXXRecordDecl() + : Y->getAsCXXRecordDecl(); + if (!XRD || !YRD) + return true; + + return XRD->isDerivedFrom(YRD); +} + +static bool isInfeasibleCast(const DynamicTypeInfo *SucceededCastInfo, + const DynamicTypeInfo *FailedCastInfo, + QualType CastFromTy, QualType CastToTy, bool CastSucceeds) { - if (!CastInfo) - return false; + // Load the proper dynamic cast information. + if (CastSucceeds && SucceededCastInfo) + CastFromTy = SucceededCastInfo->getType(); + else if (!CastSucceeds && FailedCastInfo) + CastFromTy = FailedCastInfo->getType(); + + // Upcast is infeasible. + if (CastSucceeds && isDerivedFrom(CastFromTy, CastToTy)) + return true; - return CastSucceeds ? CastInfo->fails() : CastInfo->succeeds(); + return false; } -static const NoteTag *getNoteTag(CheckerContext &C, - const DynamicCastInfo *CastInfo, - QualType CastToTy, const Expr *Object, - bool CastSucceeds, bool IsKnownCast) { +static bool isSucceededDowncast(const DynamicTypeInfo *CastInfo, + QualType CastToTy) { + const CXXRecordDecl *CurrentRD = CastToTy->getPointeeCXXRecordDecl(); + const CXXRecordDecl *PreviousRD = + CastInfo->getType()->getPointeeCXXRecordDecl(); + + if (CurrentRD == PreviousRD) + return true; + + // If the casts have a common anchestor it could not be a succeeded downcast. + for (const auto &PreviousBase : PreviousRD->bases()) + for (const auto &CurrentBase : CurrentRD->bases()) + if (PreviousBase.getType()->getAsCXXRecordDecl() == + CurrentBase.getType()->getAsCXXRecordDecl()) + return false; + + return true; +} + +namespace { +struct CastContext { + CastContext(ProgramStateRef State, const MemRegion *MR, QualType CastFromTy, + QualType CastToTy, bool Assumption, bool IsCheckedCast) { + const DynamicTypeInfo *SucceededCastInfo = getRawDynamicTypeInfo(State, MR); + const DynamicTypeInfo *FailedCastInfo = + getFailedCastInfo(State, MR, CastToTy); + + // We assume that every checked cast succeeds. + bool IsAlwaysSucceeds = + IsCheckedCast || CastFromTy->getPointeeCXXRecordDecl() == + CastToTy->getPointeeCXXRecordDecl(); + + CastSucceeds = Assumption || IsAlwaysSucceeds; + + // We need to check if the current new succeeded cast is a new downcast. + IsKnownCast = true; + if (CastSucceeds && SucceededCastInfo && !IsAlwaysSucceeds) { + CastSucceeds = isSucceededDowncast(SucceededCastInfo, CastToTy); + if (CastSucceeds) // A succeeded downcast is a new assumption. + IsKnownCast = false; + } + + if (IsKnownCast) + IsKnownCast = IsAlwaysSucceeds || SucceededCastInfo || + (!CastSucceeds && FailedCastInfo); + + CastInfo = CastSucceeds ? SucceededCastInfo : FailedCastInfo; + + IsInfeasible = isInfeasibleCast(SucceededCastInfo, FailedCastInfo, + CastFromTy, CastToTy, CastSucceeds); + } + + bool IsKnownCast, CastSucceeds, IsInfeasible; + const DynamicTypeInfo *CastInfo; +}; +} // namespace + +constexpr llvm::StringLiteral Vowels = "aeiou"; + +static const NoteTag *getNoteTag(CheckerContext &C, const Expr *Object, + QualType CastToTy, CastContext &CastCtx) { + QualType KnownCastToTy; + if (CastCtx.CastInfo) + KnownCastToTy = CastCtx.CastInfo->getType(); + std::string CastToName = - CastInfo ? CastInfo->to()->getPointeeCXXRecordDecl()->getNameAsString() - : CastToTy->getPointeeCXXRecordDecl()->getNameAsString(); + CastCtx.CastInfo && CastCtx.IsKnownCast + ? KnownCastToTy->getPointeeCXXRecordDecl()->getNameAsString() + : CastToTy->getPointeeCXXRecordDecl()->getNameAsString(); Object = Object->IgnoreParenImpCasts(); return C.getNoteTag( @@ -114,24 +190,25 @@ SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); - if (!IsKnownCast) + if (!CastCtx.IsKnownCast) Out << "Assuming "; if (const auto *DRE = dyn_cast(Object)) { Out << '\'' << DRE->getDecl()->getNameAsString() << '\''; } else if (const auto *ME = dyn_cast(Object)) { - Out << (IsKnownCast ? "Field '" : "field '") + Out << (CastCtx.IsKnownCast ? "Field '" : "field '") << ME->getMemberDecl()->getNameAsString() << '\''; } else { - Out << (IsKnownCast ? "The object" : "the object"); + Out << (CastCtx.IsKnownCast ? "The object" : "the object"); } - Out << ' ' << (CastSucceeds ? "is a" : "is not a") << " '" << CastToName - << '\''; + Out << ' ' << (CastCtx.CastSucceeds ? "is " : "is not ") + << (Vowels.contains_lower(CastToName[0]) ? "an" : "a") << " '" + << CastToName << '\''; return Out.str(); }, - /*IsPrunable=*/true); + /*IsPrunable=*/!CastCtx.CastSucceeds); } //===----------------------------------------------------------------------===// @@ -157,12 +234,14 @@ bool IsNonNullReturn, bool IsCheckedCast = false) { ProgramStateRef State = C.getState()->assume(DV, IsNonNullParam); - if (!State) + if (!State) { + C.generateSink(C.getState(), C.getPredecessor()); return; + } const Expr *Object; QualType CastFromTy; - QualType CastToTy = Call.getResultType(); + QualType CastToTy = Call.getResultType().getCanonicalType(); if (Call.getNumArgs() > 0) { Object = Call.getArgExpr(0); @@ -182,35 +261,24 @@ } const MemRegion *MR = DV.getAsRegion(); - const DynamicCastInfo *CastInfo = - getDynamicCastInfo(State, MR, CastFromTy, CastToTy); - - // We assume that every checked cast succeeds. - bool CastSucceeds = IsCheckedCast || CastFromTy == CastToTy; - if (!CastSucceeds) { - if (CastInfo) - CastSucceeds = IsNonNullReturn && CastInfo->succeeds(); - else - CastSucceeds = IsNonNullReturn; - } + CastContext CastCtx(State, MR, CastFromTy, CastToTy, + /*Assumption=*/IsNonNullReturn, IsCheckedCast); - // Check for infeasible casts. - if (isInfeasibleCast(CastInfo, CastSucceeds)) { + if (CastCtx.IsInfeasible) { C.generateSink(State, C.getPredecessor()); return; } - // Store the type and the cast information. - bool IsKnownCast = CastInfo || IsCheckedCast || CastFromTy == CastToTy; - if (!IsKnownCast || IsCheckedCast) - State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, - CastSucceeds); + State = setDynamicTypeAndCastInfo(State, MR, CastToTy, CastCtx.CastInfo, + CastCtx.CastSucceeds); + + SVal V = CastCtx.CastSucceeds + ? C.getSValBuilder().evalCast(DV, CastToTy, CastFromTy) + : C.getSValBuilder().makeNull(); - SVal V = CastSucceeds ? C.getSValBuilder().evalCast(DV, CastToTy, CastFromTy) - : C.getSValBuilder().makeNull(); C.addTransition( State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), V, false), - getNoteTag(C, CastInfo, CastToTy, Object, CastSucceeds, IsKnownCast)); + getNoteTag(C, Object, CastToTy, CastCtx)); } static void addInstanceOfTransition(const CallEvent &Call, @@ -220,6 +288,7 @@ const FunctionDecl *FD = Call.getDecl()->getAsFunction(); QualType CastFromTy = Call.parameters()[0]->getType(); QualType CastToTy = FD->getTemplateSpecializationArgs()->get(0).getAsType(); + if (CastFromTy->isPointerType()) CastToTy = C.getASTContext().getPointerType(CastToTy); else if (CastFromTy->isReferenceType()) @@ -228,31 +297,21 @@ return; const MemRegion *MR = DV.getAsRegion(); - const DynamicCastInfo *CastInfo = - getDynamicCastInfo(State, MR, CastFromTy, CastToTy); - - bool CastSucceeds; - if (CastInfo) - CastSucceeds = IsInstanceOf && CastInfo->succeeds(); - else - CastSucceeds = IsInstanceOf || CastFromTy == CastToTy; + CastContext CastCtx(State, MR, CastFromTy, CastToTy, + /*Assumption=*/IsInstanceOf, /*IsCheckedCast=*/false); - if (isInfeasibleCast(CastInfo, CastSucceeds)) { + if (CastCtx.IsInfeasible) { C.generateSink(State, C.getPredecessor()); return; } - // Store the type and the cast information. - bool IsKnownCast = CastInfo || CastFromTy == CastToTy; - if (!IsKnownCast) - State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, - IsInstanceOf); + State = setDynamicTypeAndCastInfo(State, MR, CastToTy, CastCtx.CastInfo, + CastCtx.CastSucceeds); C.addTransition( State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), - C.getSValBuilder().makeTruthVal(CastSucceeds)), - getNoteTag(C, CastInfo, CastToTy, Call.getArgExpr(0), CastSucceeds, - IsKnownCast)); + C.getSValBuilder().makeTruthVal(CastCtx.CastSucceeds)), + getNoteTag(C, Call.getArgExpr(0), CastToTy, CastCtx)); } //===----------------------------------------------------------------------===// Index: clang/lib/StaticAnalyzer/Core/DynamicType.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -27,18 +27,22 @@ REGISTER_MAP_WITH_PROGRAMSTATE(DynamicTypeMap, const clang::ento::MemRegion *, clang::ento::DynamicTypeInfo) -/// A set factory of dynamic cast informations. -REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicCastInfo) +/// A set factory of dynamic cast informations as type informations. +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicTypeInfo) -/// A map from symbols to cast informations. -REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *, +/// A map from symbols to failed cast informations. +REGISTER_MAP_WITH_PROGRAMSTATE(FailedCastMap, const clang::ento::MemRegion *, CastSet) namespace clang { namespace ento { +const MemRegion *getSimplifiedRegion(const MemRegion *MR) { + return MR->StripCasts(); +} + DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR) { - MR = MR->StripCasts(); + MR = getSimplifiedRegion(MR); // Look up the dynamic type in the GDM. if (const DynamicTypeInfo *DTI = State->get(MR)) @@ -58,27 +62,40 @@ const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR) { + if (!MR) + return nullptr; + + MR = getSimplifiedRegion(MR); return State->get(MR); } -const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, - const MemRegion *MR, - QualType CastFromTy, - QualType CastToTy) { - const auto *Lookup = State->get().lookup(MR); +const DynamicTypeInfo *getFailedCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastToTy) { + if (!MR) + return nullptr; + + MR = getSimplifiedRegion(MR); + const auto *Lookup = State->get().lookup(MR); if (!Lookup) return nullptr; - for (const DynamicCastInfo &Cast : *Lookup) - if (Cast.equals(CastFromTy, CastToTy)) + for (const DynamicTypeInfo &Cast : *Lookup) { + if (Cast.getType()->getPointeeCXXRecordDecl() == + CastToTy->getPointeeCXXRecordDecl()) return &Cast; + } return nullptr; } ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, DynamicTypeInfo NewTy) { - State = State->set(MR->StripCasts(), NewTy); + assert((NewTy.getType()->isAnyPointerType() || + NewTy.getType()->isReferenceType()) && + "DynamicTypeInfo should always be a pointer."); + MR = getSimplifiedRegion(MR); + State = State->set(MR, NewTy); assert(State); return State; } @@ -90,29 +107,31 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, const MemRegion *MR, - QualType CastFromTy, QualType CastToTy, + const DynamicTypeInfo *CastInfo, bool CastSucceeds) { if (!MR) return State; - if (CastSucceeds) { - assert((CastToTy->isAnyPointerType() || CastToTy->isReferenceType()) && - "DynamicTypeInfo should always be a pointer."); - State = State->set(MR, CastToTy); - } + MR = getSimplifiedRegion(MR); - DynamicCastInfo::CastResult ResultKind = - CastSucceeds ? DynamicCastInfo::CastResult::Success - : DynamicCastInfo::CastResult::Failure; + if (CastSucceeds) { + State = setDynamicTypeInfo(State, MR, CastToTy); + } else { + CastSet::Factory &F = State->get_context(); - CastSet::Factory &F = State->get_context(); + const CastSet *TempSet = State->get(MR); + CastSet Set = TempSet ? *TempSet : F.getEmptySet(); - const CastSet *TempSet = State->get(MR); - CastSet Set = TempSet ? *TempSet : F.getEmptySet(); + // If the 'CastToTy' is more derived remove the old cast information. + if (CastInfo && CastToTy->getPointeeCXXRecordDecl()->isDerivedFrom( + CastInfo->getType()->getPointeeCXXRecordDecl())) { + Set = F.remove(Set, *CastInfo); + } - Set = F.add(Set, {CastFromTy, CastToTy, ResultKind}); - State = State->set(MR, Set); + Set = F.add(Set, CastToTy); + State = State->set(MR, Set); + } assert(State); return State; @@ -123,7 +142,7 @@ SymbolReaper &SR) { for (const auto &Elem : Map) if (!SR.isLiveRegion(Elem.first)) - State = State->remove(Elem.first); + State = State->remove(Elem.first); return State; } @@ -133,7 +152,7 @@ } ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) { - return removeDead(State, State->get(), SR); + return removeDead(State, State->get(), SR); } static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, @@ -175,9 +194,9 @@ static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, const char *NL, unsigned int Space, bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_casts\": "; + Indent(Out, Space, IsDot) << "\"failed_casts\": "; - const DynamicCastMapTy &Map = State->get(); + const FailedCastMapTy &Map = State->get(); if (Map.isEmpty()) { Out << "null," << NL; return; @@ -185,7 +204,7 @@ ++Space; Out << '[' << NL; - for (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { + for (FailedCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { const MemRegion *MR = I->first; const CastSet &Set = I->second; @@ -195,16 +214,14 @@ } else { ++Space; Out << '[' << NL; + Indent(Out, Space, IsDot); for (CastSet::iterator SI = Set.begin(); SI != Set.end(); ++SI) { - Indent(Out, Space, IsDot) - << "{ \"from\": \"" << SI->from().getAsString() << "\", \"to\": \"" - << SI->to().getAsString() << "\", \"kind\": \"" - << (SI->succeeds() ? "success" : "fail") << "\" }"; + Out << '\"' << SI->getType().getAsString() << '\"'; if (std::next(SI) != Set.end()) - Out << ','; - Out << NL; + Out << ", "; } + Out << NL; --Space; Indent(Out, Space, IsDot) << ']'; } Index: clang/test/Analysis/cast-value-hierarchy.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/cast-value-hierarchy.cpp @@ -0,0 +1,54 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -verify %s + +#include "Inputs/llvm.h" + +using namespace llvm; + +void clang_analyzer_numTimesReached(); +void clang_analyzer_printState(); +void clang_analyzer_warnIfReached(); +void clang_analyzer_eval(bool); + +struct A {}; // A +struct B : A {}; // / \. +struct C : A {}; // B C +struct D : B {}; // / \. +struct E : B {}; // D E +struct F : D, E {}; // \ / \. +struct G : E {}; // F G + +void test_downcast(const A *a) { + const B *b = dyn_cast(a); + + if (dyn_cast(b)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +} + +void test_isa_downcast(const A *a) { + if (isa(a)) { + if (isa(a)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + } +} + +void test_diamond(const A *a) { + if (isa(a)) + if (isa(a) || isa(a)) + if (isa(a)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +} + +void test_upcast(const A *a) { + const E *e = dyn_cast(a); + + if (dyn_cast(e)) + clang_analyzer_warnIfReached(); // no-warning +} + +void test_isa_upcast(const A *a) { + if (isa(a)) + if (isa(a)) + clang_analyzer_warnIfReached(); // no-warning +} Index: clang/test/Analysis/cast-value-notes.cpp =================================================================== --- clang/test/Analysis/cast-value-notes.cpp +++ clang/test/Analysis/cast-value-notes.cpp @@ -37,12 +37,6 @@ return; } - if (dyn_cast_or_null(C)) { - // expected-note@-1 {{Assuming 'C' is not a 'Triangle'}} - // expected-note@-2 {{Taking false branch}} - return; - } - if (isa(C)) { // expected-note@-1 {{'C' is not a 'Triangle'}} // expected-note@-2 {{Taking false branch}} @@ -65,14 +59,8 @@ // expected-note@-1 {{'S' is a 'Circle'}} // expected-note@-2 {{'C' initialized here}} - if (!isa(C)) { - // expected-note@-1 {{Assuming 'C' is a 'Triangle'}} - // expected-note@-2 {{Taking false branch}} - return; - } - - if (!isa(C)) { - // expected-note@-1 {{'C' is a 'Triangle'}} + if (isa(C)) { + // expected-note@-1 {{'C' is not a 'Triangle'}} // expected-note@-2 {{Taking false branch}} return; } Index: clang/test/Analysis/cast-value-state-dump.cpp =================================================================== --- clang/test/Analysis/cast-value-state-dump.cpp +++ clang/test/Analysis/cast-value-state-dump.cpp @@ -11,19 +11,31 @@ class Triangle : public Shape {}; class Circle : public Shape {}; class Square : public Shape {}; +class Octagram : public Shape {}; } // namespace clang using namespace llvm; using namespace clang; void evalNonNullParamNonNullReturn(const Shape *S) { + if (isa(S)) { + // expected-note@-1 {{Assuming 'S' is not a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (isa(S)) { + // expected-note@-1 {{Assuming 'S' is not an 'Octagram'}} + // expected-note@-2 {{Taking false branch}} + return; + } + const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming 'S' is a 'Circle'}} // expected-note@-2 {{'C' initialized here}} - // FIXME: We assumed that 'S' is a 'Circle' therefore it is not a 'Square'. if (dyn_cast_or_null(S)) { - // expected-note@-1 {{Assuming 'S' is not a 'Square'}} + // expected-note@-1 {{'S' is not a 'Square'}} // expected-note@-2 {{Taking false branch}} return; } @@ -33,11 +45,11 @@ // CHECK: "dynamic_types": [ // CHECK-NEXT: { "region": "SymRegion{reg_$0}", "dyn_type": "const class clang::Circle", "sub_classable": true } // CHECK-NEXT: ], - // CHECK-NEXT: "dynamic_casts": [ - // CHECK: { "region": "SymRegion{reg_$0}", "casts": [ - // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Circle *", "kind": "success" }, - // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Square *", "kind": "fail" } + // CHECK-NEXT: "failed_casts": [ + // CHECK-NEXT: { "region": "SymRegion{reg_$0}", "casts": [ + // CHECK-NEXT: "const class clang::Square *", "class clang::Triangle *", "class clang::Octagram *" // CHECK-NEXT: ]} + // CHECK-NEXT: ], (void)(1 / !C); // expected-note@-1 {{'C' is non-null}} Index: clang/test/Analysis/expr-inspection.c =================================================================== --- clang/test/Analysis/expr-inspection.c +++ clang/test/Analysis/expr-inspection.c @@ -38,7 +38,7 @@ // CHECK-NEXT: { "symbol": "reg_$0", "range": "{ [-2147483648, 13] }" } // CHECK-NEXT: ], // CHECK-NEXT: "dynamic_types": null, -// CHECK-NEXT: "dynamic_casts": null, +// CHECK-NEXT: "failed_casts": null, // CHECK-NEXT: "constructing_objects": null, // CHECK-NEXT: "checker_messages": null // CHECK-NEXT: }