Index: clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -16,168 +16,235 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "llvm/ADT/Optional.h" +#include using namespace clang; using namespace ento; namespace { class CastValueChecker : public Checker { + enum class CastKind { Checking, Sugar }; + using CastCheck = std::function; + using CheckKindPair = std::pair; + 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}, - {{{"llvm", "cast_or_null"}, 1}, &CastValueChecker::evalCastOrNull}, + // These are known in the LLVM project. The pairs are in the following form: + // {{{namespace, call}, argument-count}, callback} + const CallDescriptionMap CDM = { + {{{"llvm", "cast"}, 1}, + {&CastValueChecker::evalCast, CastKind::Checking}}, + {{{"llvm", "dyn_cast"}, 1}, + {&CastValueChecker::evalDynCast, CastKind::Checking}}, + {{{"llvm", "cast_or_null"}, 1}, + {&CastValueChecker::evalCastOrNull, CastKind::Checking}}, {{{"llvm", "dyn_cast_or_null"}, 1}, - &CastValueChecker::evalDynCastOrNull}}; + {&CastValueChecker::evalDynCastOrNull, CastKind::Checking}}, + {{{"clang", "castAs"}, 0}, + {&CastValueChecker::evalCastAs, CastKind::Sugar}}, + {{{"clang", "getAs"}, 0}, + {&CastValueChecker::evalGetAs, CastKind::Sugar}}}; - void evalCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; - void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV, + void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const; + void evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const; }; } // namespace static std::string getCastName(const Expr *Cast) { - 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; + QualType Ty = Cast->getType(); + if (const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl()) + return RD->getNameAsString(); - State = State->BindExpr(CE, C.getLocationContext(), ParamDV, false); + return Ty->getPointeeCXXRecordDecl()->getNameAsString(); +} - std::string CastFromName = getCastName(CE->getArg(0)); +static const NoteTag *getCastTag(bool IsNullReturn, const CallExpr *CE, + CheckerContext &C, bool IsSimpleCast = false) { + 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, + IsSimpleCast](BugReport &) -> std::string { SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); - Out << "Assuming dynamic cast from '" << CastFromName << "' to '" - << CastToName << "' succeeds"; - return Out.str(); - }, - /*IsPrunable=*/true); - - C.addTransition(State, CastTag); -} - -static void evalNonNullParamNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, true); - if (!State) - return; + Out << (!IsSimpleCast ? "Assuming dynamic cast " : "Dynamic cast "); + if (CastFromName) + Out << "from '" << *CastFromName << "' "; - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); + Out << "to '" << CastToName << "' " + << (!IsNullReturn ? "succeeds" : "fails"); - 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); +static ProgramStateRef getState(bool IsNullReturn, + DefinedOrUnknownSVal ReturnDV, + const CallExpr *CE, ProgramStateRef State, + CheckerContext &C) { + return State->BindExpr( + CE, C.getLocationContext(), + IsNullReturn ? C.getSValBuilder().makeNull() : ReturnDV, false); } -static void evalNullParamNullReturn(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, - CheckerContext &C) { - ProgramStateRef State = C.getState()->assume(ParamDV, false); - if (!State) - return; +//===----------------------------------------------------------------------===// +// Evaluating cast, dyn_cast, cast_or_null, dyn_cast_or_null. +//===----------------------------------------------------------------------===// - State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull(), false); +static void evalNonNullParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal DV, + CheckerContext &C, + bool IsSimpleCast = false) { + bool IsNullReturn = false; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, DV, CE, State, C), + getCastTag(IsNullReturn, CE, C, IsSimpleCast)); +} - const NoteTag *CastTag = - C.getNoteTag("Assuming null pointer is passed into cast", - /*IsPrunable=*/true); +static void evalNonNullParamNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal DV, + CheckerContext &C) { + bool IsNullReturn = true; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, DV, CE, State, C), + getCastTag(IsNullReturn, CE, C)); +} - C.addTransition(State, CastTag); +static void evalNullParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) { + if (ProgramStateRef State = C.getState()->assume(DV, false)) + C.addTransition(getState(/*IsNullReturn=*/true, DV, CE, State, C), + C.getNoteTag("Assuming null pointer is passed into cast", + /*IsPrunable=*/true)); } -void CastValueChecker::evalCast(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, +void CastValueChecker::evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C, /*IsSimpleCast=*/true); } -void CastValueChecker::evalDynCast(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, +void CastValueChecker::evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNonNullParamNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C); + evalNonNullParamNullReturn(CE, DV, C); } void CastValueChecker::evalCastOrNull(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, + DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNullParamNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C); + evalNullParamNullReturn(CE, DV, C); } void CastValueChecker::evalDynCastOrNull(const CallExpr *CE, - DefinedOrUnknownSVal ParamDV, + DefinedOrUnknownSVal DV, CheckerContext &C) const { - evalNonNullParamNonNullReturn(CE, ParamDV, C); - evalNonNullParamNullReturn(CE, ParamDV, C); - evalNullParamNullReturn(CE, ParamDV, C); + evalNonNullParamNonNullReturn(CE, DV, C); + evalNonNullParamNullReturn(CE, DV, C); + evalNullParamNullReturn(CE, DV, C); +} + +//===----------------------------------------------------------------------===// +// Evaluating castAs, getAs. +//===----------------------------------------------------------------------===// + +static void evalZeroParamNonNullReturn(const CallExpr *CE, + DefinedOrUnknownSVal DV, + CheckerContext &C, + bool IsSimpleCast = false) { + bool IsNullReturn = false; + if (ProgramStateRef State = C.getState()->assume(DV, true)) + C.addTransition(getState(IsNullReturn, DV, CE, C.getState(), C), + getCastTag(IsNullReturn, CE, C, IsSimpleCast)); +} + +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, DV, CE, C.getState(), C), + getCastTag(IsNullReturn, CE, C)); +} + +void CastValueChecker::evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalZeroParamNonNullReturn(CE, DV, C, /*IsSimpleCast=*/true); +} + +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); - if (!Check) + const auto *Lookup = CDM.lookup(Call); + if (!Lookup) return false; - const auto *CE = cast(Call.getOriginExpr()); - if (!CE) + // If we cannot obtain the call's class we cannot be sure how to model it. + if (!Call.getResultType()->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; + const CastCheck *Check = &Lookup->first; + CastKind Kind = Lookup->second; + + const auto *CE = cast(Call.getOriginExpr()); + Optional DV; + + switch (Kind) { + case CastKind::Checking: + // If we cannot obtain the arg's class we cannot be sure how to model it. + if (!Call.parameters()[0]->getType()->getPointeeCXXRecordDecl()) + return false; + + DV = Call.getArgSVal(0).getAs(); + break; + + case CastKind::Sugar: + // If we cannot obtain the 'InstanceCall' we cannot be sure how to model it. + const auto *InstanceCall = dyn_cast(&Call); + if (!InstanceCall) + return false; + + DV = InstanceCall->getCXXThisVal().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/test/Analysis/cast-value.cpp =================================================================== --- clang/test/Analysis/cast-value.cpp +++ clang/test/Analysis/cast-value.cpp @@ -20,14 +20,26 @@ const X *cast_or_null(Y Value); template -const X *dyn_cast_or_null(Y Value); +const X *dyn_cast_or_null(Y *Value); + +template +const X *dyn_cast_or_null(const Y &Value); } // namespace llvm -using namespace llvm; +namespace clang { +struct Shape { + template + const T *castAs() const; -class Shape {}; + 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,8 +103,44 @@ if (!S) clang_analyzer_eval(!C); // logic-warning {{TRUE}} } +} // namespace test_dyn_cast_or_null -void evalNonNullParamNonNullReturn(const Shape *S) { +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}} + + 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}} + + if (S && !C) + clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_get_as + +namespace test_notes { +void evalNonNullParamNonNullReturnReference(const Shape &S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}} // expected-note@-2 {{Assuming pointer value is null}} @@ -105,6 +153,19 @@ // logic-warning@-4 {{Division by zero}} } +void evalNonNullParamNonNullReturn(const Shape *S) { + const auto *C = cast(S); + // expected-note@-1 {{Dynamic cast from 'Shape' to 'Circle' succeeds}} + // expected-note@-2 {{Assuming pointer value is null}} + // expected-note@-3 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + void evalNonNullParamNullReturn(const Shape *S) { const auto *C = dyn_cast_or_null(S); // expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}} @@ -134,4 +195,40 @@ // expected-warning@-2 {{Division by zero}} // logic-warning@-3 {{Division by zero}} } -} // namespace test_dyn_cast_or_null + +void evalZeroParamNonNullReturnPointer(const Shape *S) { + const auto *C = S->castAs(); + // expected-note@-1 {{Assuming pointer value is null}} + // expected-note@-2 {{Dynamic cast to 'Circle' succeeds}} + // expected-note@-3 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + +void evalZeroParamNonNullReturn(const Shape &S) { + const auto *C = S.castAs(); + // expected-note@-1 {{Dynamic cast to 'Circle' succeeds}} + // expected-note@-2 {{'C' initialized here}} + + (void)(1 / !(bool)C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + // logic-warning@-4 {{Division by zero}} +} + +void evalZeroParamNullReturn(const Shape &S) { + const auto *C = S.getAs(); + // expected-note@-1 {{Assuming dynamic cast to 'Circle' fails}} + // 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