Index: clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -22,27 +22,35 @@ namespace { class CastValueChecker : public Checker { + enum class CastKind { Checking, Sugar }; + using CastCheck = std::function; public: - // We have three cases to evaluate a cast: + // We have five cases to evaluate a cast: // 1) The parameter is non-null, the return value is non-null // 2) The parameter is non-null, the return value is null // 3) The parameter is null, the return value is null - // // cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3. + // + // 4) castAs: has no parameter, the return value is non-null. + // 5) getAs: has no parameter, the return value is null or non-null. bool evalCall(const CallEvent &Call, CheckerContext &C) const; private: - // These are known in the LLVM project. - const CallDescriptionMap CDM = { - {{{"llvm", "cast"}, 1}, &CastValueChecker::evalCast}, - {{{"llvm", "dyn_cast"}, 1}, &CastValueChecker::evalDynCast}, + // These are known in the LLVM project. The pairs are in the following form: + // {{{namespace, call}, argument-count}, callback} + const CallDescriptionMap CheckingCastCDM = { + {{{"llvm", "dyn_cast_or_null"}, 1}, &CastValueChecker::evalDynCastOrNull}, {{{"llvm", "cast_or_null"}, 1}, &CastValueChecker::evalCastOrNull}, - {{{"llvm", "dyn_cast_or_null"}, 1}, - &CastValueChecker::evalDynCastOrNull}}; + {{{"llvm", "dyn_cast"}, 1}, &CastValueChecker::evalDynCast}, + {{{"llvm", "cast"}, 1}, &CastValueChecker::evalCast}}; + + const CallDescriptionMap SugarCastCDM = { + {{{"clang", "castAs"}, 0}, &CastValueChecker::evalCastAs}, + {{{"clang", "getAs"}, 0}, &CastValueChecker::evalGetAs}}; void evalCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, CheckerContext &C) const; @@ -52,6 +60,11 @@ CheckerContext &C) const; void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, CheckerContext &C) const; + + void evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const; }; } // namespace @@ -59,74 +72,77 @@ return Cast->getType()->getPointeeCXXRecordDecl()->getNameAsString(); } -static void evalNonNullParamNonNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, true); - if (!State) - return; - - State = State->BindExpr(CE, C.getLocationContext(), ParamDV, false); - - std::string CastFromName = getCastName(CE->getArg(0)); +static const NoteTag *getCastTag(bool IsNullReturn, const CallExpr *CE, + CheckerContext &C) { + Optional CastFromName = (CE->getNumArgs() > 0) + ? getCastName(CE->getArg(0)) + : Optional(); std::string CastToName = getCastName(CE); - const NoteTag *CastTag = C.getNoteTag( - [CastFromName, CastToName](BugReport &) -> std::string { + return C.getNoteTag( + [CastFromName, CastToName, IsNullReturn](BugReport &) -> std::string { SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); - Out << "Assuming dynamic cast from '" << CastFromName << "' to '" - << CastToName << "' succeeds"; + Out << "Assuming dynamic cast "; + if (CastFromName) + Out << "from '" << *CastFromName << "' "; + + Out << "to '" << CastToName << "' " + << (!IsNullReturn ? "succeeds" : "fails"); + return Out.str(); }, /*IsPrunable=*/true); +} - C.addTransition(State, CastTag); +static ProgramStateRef getState(bool IsNullReturn, + Optional ReturnDV, + const CallExpr *CE, ProgramStateRef State, + CheckerContext &C) { + SVal V; + const LocationContext *LCtx = C.getLocationContext(); + if (IsNullReturn) { + V = C.getSValBuilder().makeNull(); + } else if (ReturnDV) { + V = *ReturnDV; + } else { + QualType Ty = CE->getCallReturnType(C.getASTContext()); + V = C.getSValBuilder().makeTruthVal(true, Ty); + } + + return State->BindExpr(CE, LCtx, V, false); +} + +//===----------------------------------------------------------------------===// +// Evaluating cast, dyn_cast, cast_or_null, dyn_cast_or_null. +//===----------------------------------------------------------------------===// + +static void evalNonNullParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal ParamDV, + CheckerContext &C) { + bool IsNullReturn = false; + if (ProgramStateRef State = C.getState()->assume(ParamDV, true)) + C.addTransition(getState(IsNullReturn, ParamDV, CE, State, C), + getCastTag(IsNullReturn, CE, C)); } static void evalNonNullParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, true); - if (!State) - return; - - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); - - std::string CastFromName = getCastName(CE->getArg(0)); - std::string CastToName = getCastName(CE); - - const NoteTag *CastTag = C.getNoteTag( - [CastFromName, CastToName](BugReport &) -> std::string { - SmallString<128> Msg; - llvm::raw_svector_ostream Out(Msg); - - Out << "Assuming dynamic cast from '" << CastFromName << "' to '" - << CastToName << "' fails"; - return Out.str(); - }, - /*IsPrunable=*/true); - - C.addTransition(State, CastTag); + bool IsNullReturn = true; + if (ProgramStateRef State = C.getState()->assume(ParamDV, true)) + C.addTransition(getState(IsNullReturn, ParamDV, CE, State, C), + getCastTag(IsNullReturn, CE, C)); } static void evalNullParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, false); - if (!State) - return; - - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); - - const NoteTag *CastTag = - C.getNoteTag("Assuming null pointer is passed into cast", - /*IsPrunable=*/true); - - C.addTransition(State, CastTag); + if (ProgramStateRef State = C.getState()->assume(ParamDV, false)) + C.addTransition(getState(/*IsNullReturn=*/true, ParamDV, CE, State, C), + C.getNoteTag("Assuming null pointer is passed into cast", + /*IsPrunable=*/true)); } void CastValueChecker::evalCast(const CallExpr *CE, @@ -157,27 +173,82 @@ evalNullParamNullReturn(CE, ParamDV, C); } +//===----------------------------------------------------------------------===// +// Evaluating castAs, getAs. +//===----------------------------------------------------------------------===// + +static void evalZeroParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal DV, + CheckerContext &C) { + bool IsNullReturn = false; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, None, CE, C.getState(), C), + getCastTag(IsNullReturn, CE, C)); +} + +static void evalZeroParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) { + bool IsNullReturn = true; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, None, CE, C.getState(), C), + getCastTag(IsNullReturn, CE, C)); +} + +void CastValueChecker::evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalZeroParamNonNullReturn(CE, DV, C); +} + +void CastValueChecker::evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalZeroParamNonNullReturn(CE, DV, C); + evalZeroParamNullReturn(CE, DV, C); +} + bool CastValueChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const CastCheck *Check = CDM.lookup(Call); + const CastCheck *Check = CheckingCastCDM.lookup(Call); + CastKind Kind = CastKind::Checking; + + if (!Check) { + Check = SugarCastCDM.lookup(Call); + Kind = CastKind::Sugar; + } + if (!Check) return false; + // If we cannot obtain the call's class we cannot be sure how to model it. const auto *CE = cast(Call.getOriginExpr()); - if (!CE) + if (!CE->getType()->getPointeeCXXRecordDecl()) return false; - // If we cannot obtain both of the classes we cannot be sure how to model it. - if (!CE->getType()->getPointeeCXXRecordDecl() || - !CE->getArg(0)->getType()->getPointeeCXXRecordDecl()) - return false; + Optional DV; + + switch (Kind) { + case CastKind::Checking: + // If we cannot obtain the arg's class we cannot be sure how to model it. + if (!CE->getArg(0)->getType()->getPointeeCXXRecordDecl()) + return false; + + DV = Call.getArgSVal(0).getAs(); + break; + + case CastKind::Sugar: + // If we cannot obtain 'MCE' we cannot be sure how to model it. + const auto *MCE = dyn_cast(CE->IgnoreParenImpCasts()); + if (!MCE) + return false; + + DV = C.getSVal(MCE->getImplicitObjectArgument()) + .getAs(); + break; + } - SVal ParamV = Call.getArgSVal(0); - auto ParamDV = ParamV.getAs(); - if (!ParamDV) + if (!DV) return false; - (*Check)(this, CE, *ParamDV, C); + (*Check)(this, CE, *DV, C); return true; } Index: clang/lib/StaticAnalyzer/Core/CallEvent.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -714,7 +714,8 @@ return UnknownVal(); SVal ThisVal = getSVal(Base); - assert(ThisVal.isUnknownOrUndef() || ThisVal.getAs()); + assert(ThisVal.isUnknownOrUndef() || ThisVal.getAs() || + ThisVal.getAs()->getValue().getExtValue() == 1); return ThisVal; } Index: clang/test/Analysis/cast-value.cpp =================================================================== --- clang/test/Analysis/cast-value.cpp +++ clang/test/Analysis/cast-value.cpp @@ -23,11 +23,20 @@ const X *dyn_cast_or_null(Y Value); } // namespace llvm -using namespace llvm; - -class Shape {}; +namespace clang { +struct Shape { + template + const T *castAs() const; + + template + const T *getAs() const; +}; class Triangle : public Shape {}; class Circle : public Shape {}; +} // namespace clang + +using namespace llvm; +using namespace clang; namespace test_cast { void evalLogic(const Shape *S) { @@ -91,7 +100,45 @@ if (!S) clang_analyzer_eval(!C); // logic-warning {{TRUE}} } +} // namespace test_dyn_cast_or_null + +namespace test_cast_as { +void evalLogic(const Shape *S) { + const Circle *C = S->castAs(); + clang_analyzer_numTimesReached(); // logic-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); + // logic-warning@-1 {{TRUE}} + // logic-warning@-2 {{FALSE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast_as + +namespace test_get_as { +void evalLogic(const Shape *S) { + const Circle *C = S->getAs(); + clang_analyzer_numTimesReached(); // logic-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); + // logic-warning@-1 {{TRUE}} + // logic-warning@-2 {{FALSE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_get_as +namespace test_notes { void evalNonNullParamNonNullReturn(const Shape *S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} @@ -134,4 +181,15 @@ // expected-warning@-2 {{Division by zero}} // logic-warning@-3 {{Division by zero}} } -} // namespace test_dyn_cast_or_null + +void evalZeroParamNonNullReturn(const Shape *S) { + const auto *C = S->castAs(); + // expected-note@-1 {{Assuming dynamic cast to 'Circle' succeeds}} + // expected-note@-2 {{'C' initialized to a null pointer value}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} + // logic-warning@-3 {{Division by zero}} +} +} // namespace test_notes