Skip to content

Commit 27cf666

Browse files
author
Csaba Dabis
committedJul 9, 2019
[analyzer] CastValueChecker: Model casts
Summary: It models the LLVM casts: - `cast<>` - `dyn_cast<>` - `cast_or_null<>` - `dyn_cast_or_null<>` It has a very basic support without checking the `classof()` function. Reviewed By: NoQ Tags: #clang Differential Revision: https://reviews.llvm.org/D64374 llvm-svn: 365582
1 parent abce8c4 commit 27cf666

File tree

6 files changed

+349
-5
lines changed

6 files changed

+349
-5
lines changed
 

Diff for: ‎clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

+13-4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def LLVMAlpha : Package<"llvm">, ParentPackage<Alpha>;
100100
// intended for API modeling that is not controlled by the target triple.
101101
def APIModeling : Package<"apiModeling">, Hidden;
102102
def GoogleAPIModeling : Package<"google">, ParentPackage<APIModeling>, Hidden;
103+
def LLVMAPIModeling : Package<"llvm">, ParentPackage<APIModeling>, Hidden;
103104

104105
def Debug : Package<"debug">, Hidden;
105106

@@ -274,10 +275,6 @@ def NullableReturnedFromNonnullChecker : Checker<"NullableReturnedFromNonnull">,
274275

275276
let ParentPackage = APIModeling in {
276277

277-
def ReturnValueChecker : Checker<"ReturnValue">,
278-
HelpText<"Model the guaranteed boolean return value of function calls">,
279-
Documentation<NotDocumented>;
280-
281278
def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">,
282279
HelpText<"Improve modeling of the C standard library functions">,
283280
Documentation<NotDocumented>;
@@ -1109,6 +1106,18 @@ def LLVMConventionsChecker : Checker<"Conventions">,
11091106

11101107
} // end "llvm"
11111108

1109+
let ParentPackage = LLVMAPIModeling in {
1110+
1111+
def CastValueChecker : Checker<"CastValue">,
1112+
HelpText<"Model implementation of custom RTTIs">,
1113+
Documentation<NotDocumented>;
1114+
1115+
def ReturnValueChecker : Checker<"ReturnValue">,
1116+
HelpText<"Model the guaranteed boolean return value of function calls">,
1117+
Documentation<NotDocumented>;
1118+
1119+
} // end "apiModeling.llvm"
1120+
11121121
//===----------------------------------------------------------------------===//
11131122
// Checkers modeling Google APIs.
11141123
//===----------------------------------------------------------------------===//

Diff for: ‎clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h

+11
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,17 @@ class CheckerContext {
247247
IsPrunable);
248248
}
249249

250+
/// A shorthand version of getNoteTag that accepts a plain note.
251+
///
252+
/// @param Note The note.
253+
/// @param IsPrunable Whether the note is prunable. It allows BugReporter
254+
/// to omit the note from the report if it would make the displayed
255+
/// bug path significantly shorter.
256+
const NoteTag *getNoteTag(StringRef Note, bool IsPrunable = false) {
257+
return getNoteTag(
258+
[Note](BugReporterContext &, BugReport &) { return Note; }, IsPrunable);
259+
}
260+
250261
/// Returns the word that should be used to refer to the declaration
251262
/// in the report.
252263
StringRef getDeclDescription(const Decl *D);

Diff for: ‎clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ add_clang_library(clangStaticAnalyzerCheckers
1616
CallAndMessageChecker.cpp
1717
CastSizeChecker.cpp
1818
CastToStructChecker.cpp
19+
CastValueChecker.cpp
1920
CheckObjCDealloc.cpp
2021
CheckObjCInstMethSignature.cpp
2122
CheckSecuritySyntaxOnly.cpp
+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//===- CastValueChecker - Model implementation of custom RTTIs --*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This defines CastValueChecker which models casts of custom RTTIs.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14+
#include "clang/StaticAnalyzer/Core/Checker.h"
15+
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
16+
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
17+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
18+
#include "llvm/ADT/Optional.h"
19+
20+
using namespace clang;
21+
using namespace ento;
22+
23+
namespace {
24+
class CastValueChecker : public Checker<eval::Call> {
25+
using CastCheck =
26+
std::function<void(const CastValueChecker *, const CallExpr *,
27+
DefinedOrUnknownSVal, CheckerContext &)>;
28+
29+
public:
30+
// We have three cases to evaluate a cast:
31+
// 1) The parameter is non-null, the return value is non-null
32+
// 2) The parameter is non-null, the return value is null
33+
// 3) The parameter is null, the return value is null
34+
//
35+
// cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3.
36+
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
37+
38+
private:
39+
// These are known in the LLVM project.
40+
const CallDescriptionMap<CastCheck> CDM = {
41+
{{{"llvm", "cast"}, 1}, &CastValueChecker::evalCast},
42+
{{{"llvm", "dyn_cast"}, 1}, &CastValueChecker::evalDynCast},
43+
{{{"llvm", "cast_or_null"}, 1}, &CastValueChecker::evalCastOrNull},
44+
{{{"llvm", "dyn_cast_or_null"}, 1},
45+
&CastValueChecker::evalDynCastOrNull}};
46+
47+
void evalCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV,
48+
CheckerContext &C) const;
49+
void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal ParamDV,
50+
CheckerContext &C) const;
51+
void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV,
52+
CheckerContext &C) const;
53+
void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal ParamDV,
54+
CheckerContext &C) const;
55+
};
56+
} // namespace
57+
58+
static std::string getCastName(const Expr *Cast) {
59+
return Cast->getType()->getPointeeCXXRecordDecl()->getNameAsString();
60+
}
61+
62+
static void evalNonNullParamNonNullReturn(const CallExpr *CE,
63+
DefinedOrUnknownSVal ParamDV,
64+
CheckerContext &C) {
65+
ProgramStateRef State = C.getState()->assume(ParamDV, true);
66+
if (!State)
67+
return;
68+
69+
State = State->BindExpr(CE, C.getLocationContext(), ParamDV, false);
70+
71+
std::string CastFromName = getCastName(CE->getArg(0));
72+
std::string CastToName = getCastName(CE);
73+
74+
const NoteTag *CastTag = C.getNoteTag(
75+
[CastFromName, CastToName](BugReport &) -> std::string {
76+
SmallString<128> Msg;
77+
llvm::raw_svector_ostream Out(Msg);
78+
79+
Out << "Assuming dynamic cast from '" << CastFromName << "' to '"
80+
<< CastToName << "' succeeds";
81+
return Out.str();
82+
},
83+
/*IsPrunable=*/true);
84+
85+
C.addTransition(State, CastTag);
86+
}
87+
88+
static void evalNonNullParamNullReturn(const CallExpr *CE,
89+
DefinedOrUnknownSVal ParamDV,
90+
CheckerContext &C) {
91+
ProgramStateRef State = C.getState()->assume(ParamDV, true);
92+
if (!State)
93+
return;
94+
95+
State = State->BindExpr(CE, C.getLocationContext(),
96+
C.getSValBuilder().makeNull(), false);
97+
98+
std::string CastFromName = getCastName(CE->getArg(0));
99+
std::string CastToName = getCastName(CE);
100+
101+
const NoteTag *CastTag = C.getNoteTag(
102+
[CastFromName, CastToName](BugReport &) -> std::string {
103+
SmallString<128> Msg;
104+
llvm::raw_svector_ostream Out(Msg);
105+
106+
Out << "Assuming dynamic cast from '" << CastFromName << "' to '"
107+
<< CastToName << "' fails";
108+
return Out.str();
109+
},
110+
/*IsPrunable=*/true);
111+
112+
C.addTransition(State, CastTag);
113+
}
114+
115+
static void evalNullParamNullReturn(const CallExpr *CE,
116+
DefinedOrUnknownSVal ParamDV,
117+
CheckerContext &C) {
118+
ProgramStateRef State = C.getState()->assume(ParamDV, false);
119+
if (!State)
120+
return;
121+
122+
State = State->BindExpr(CE, C.getLocationContext(),
123+
C.getSValBuilder().makeNull(), false);
124+
125+
const NoteTag *CastTag =
126+
C.getNoteTag("Assuming null pointer is passed into cast",
127+
/*IsPrunable=*/true);
128+
129+
C.addTransition(State, CastTag);
130+
}
131+
132+
void CastValueChecker::evalCast(const CallExpr *CE,
133+
DefinedOrUnknownSVal ParamDV,
134+
CheckerContext &C) const {
135+
evalNonNullParamNonNullReturn(CE, ParamDV, C);
136+
}
137+
138+
void CastValueChecker::evalDynCast(const CallExpr *CE,
139+
DefinedOrUnknownSVal ParamDV,
140+
CheckerContext &C) const {
141+
evalNonNullParamNonNullReturn(CE, ParamDV, C);
142+
evalNonNullParamNullReturn(CE, ParamDV, C);
143+
}
144+
145+
void CastValueChecker::evalCastOrNull(const CallExpr *CE,
146+
DefinedOrUnknownSVal ParamDV,
147+
CheckerContext &C) const {
148+
evalNonNullParamNonNullReturn(CE, ParamDV, C);
149+
evalNullParamNullReturn(CE, ParamDV, C);
150+
}
151+
152+
void CastValueChecker::evalDynCastOrNull(const CallExpr *CE,
153+
DefinedOrUnknownSVal ParamDV,
154+
CheckerContext &C) const {
155+
evalNonNullParamNonNullReturn(CE, ParamDV, C);
156+
evalNonNullParamNullReturn(CE, ParamDV, C);
157+
evalNullParamNullReturn(CE, ParamDV, C);
158+
}
159+
160+
bool CastValueChecker::evalCall(const CallEvent &Call,
161+
CheckerContext &C) const {
162+
const CastCheck *Check = CDM.lookup(Call);
163+
if (!Check)
164+
return false;
165+
166+
const auto *CE = cast<CallExpr>(Call.getOriginExpr());
167+
if (!CE)
168+
return false;
169+
170+
// If we cannot obtain both of the classes we cannot be sure how to model it.
171+
if (!CE->getType()->getPointeeCXXRecordDecl() ||
172+
!CE->getArg(0)->getType()->getPointeeCXXRecordDecl())
173+
return false;
174+
175+
SVal ParamV = Call.getArgSVal(0);
176+
auto ParamDV = ParamV.getAs<DefinedOrUnknownSVal>();
177+
if (!ParamDV)
178+
return false;
179+
180+
(*Check)(this, CE, *ParamDV, C);
181+
return true;
182+
}
183+
184+
void ento::registerCastValueChecker(CheckerManager &Mgr) {
185+
Mgr.registerChecker<CastValueChecker>();
186+
}
187+
188+
bool ento::shouldRegisterCastValueChecker(const LangOptions &LO) {
189+
return true;
190+
}

Diff for: ‎clang/test/Analysis/cast-value.cpp

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// RUN: %clang_analyze_cc1 \
2+
// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\
3+
// RUN: -verify=logic %s
4+
// RUN: %clang_analyze_cc1 \
5+
// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue \
6+
// RUN: -analyzer-output=text -verify %s
7+
8+
void clang_analyzer_numTimesReached();
9+
void clang_analyzer_warnIfReached();
10+
void clang_analyzer_eval(bool);
11+
12+
template <class X, class Y>
13+
const X *cast(Y Value);
14+
15+
template <class X, class Y>
16+
const X *dyn_cast(Y Value);
17+
18+
template <class X, class Y>
19+
const X *cast_or_null(Y Value);
20+
21+
template <class X, class Y>
22+
const X *dyn_cast_or_null(Y Value);
23+
24+
class Shape {};
25+
class Triangle : public Shape {};
26+
class Circle : public Shape {};
27+
28+
namespace test_cast {
29+
void evalLogic(const Shape *S) {
30+
const Circle *C = cast<Circle>(S);
31+
clang_analyzer_numTimesReached(); // logic-warning {{1}}
32+
33+
if (S && C)
34+
clang_analyzer_eval(C == S); // logic-warning {{TRUE}}
35+
36+
if (S && !C)
37+
clang_analyzer_warnIfReached(); // no-warning
38+
39+
if (!S)
40+
clang_analyzer_warnIfReached(); // no-warning
41+
}
42+
} // namespace test_cast
43+
44+
namespace test_dyn_cast {
45+
void evalLogic(const Shape *S) {
46+
const Circle *C = dyn_cast<Circle>(S);
47+
clang_analyzer_numTimesReached(); // logic-warning {{2}}
48+
49+
if (S && C)
50+
clang_analyzer_eval(C == S); // logic-warning {{TRUE}}
51+
52+
if (S && !C)
53+
clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}}
54+
55+
if (!S)
56+
clang_analyzer_warnIfReached(); // no-warning
57+
}
58+
} // namespace test_dyn_cast
59+
60+
namespace test_cast_or_null {
61+
void evalLogic(const Shape *S) {
62+
const Circle *C = cast_or_null<Circle>(S);
63+
clang_analyzer_numTimesReached(); // logic-warning {{2}}
64+
65+
if (S && C)
66+
clang_analyzer_eval(C == S); // logic-warning {{TRUE}}
67+
68+
if (S && !C)
69+
clang_analyzer_warnIfReached(); // no-warning
70+
71+
if (!S)
72+
clang_analyzer_eval(!C); // logic-warning {{TRUE}}
73+
}
74+
} // namespace test_cast_or_null
75+
76+
namespace test_dyn_cast_or_null {
77+
void evalLogic(const Shape *S) {
78+
const Circle *C = dyn_cast_or_null<Circle>(S);
79+
clang_analyzer_numTimesReached(); // logic-warning {{3}}
80+
81+
if (S && C)
82+
clang_analyzer_eval(C == S); // logic-warning {{TRUE}}
83+
84+
if (S && !C)
85+
clang_analyzer_warnIfReached(); // logic-warning {{REACHABLE}}
86+
87+
if (!S)
88+
clang_analyzer_eval(!C); // logic-warning {{TRUE}}
89+
}
90+
91+
void evalNonNullParamNonNullReturn(const Shape *S) {
92+
const auto *C = dyn_cast_or_null<Circle>(S);
93+
// expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' succeeds}}
94+
// expected-note@-2 {{Assuming pointer value is null}}
95+
// expected-note@-3 {{'C' initialized here}}
96+
97+
(void)(1 / !(bool)C);
98+
// expected-note@-1 {{'C' is non-null}}
99+
// expected-note@-2 {{Division by zero}}
100+
// expected-warning@-3 {{Division by zero}}
101+
// logic-warning@-4 {{Division by zero}}
102+
}
103+
104+
void evalNonNullParamNullReturn(const Shape *S) {
105+
const auto *C = dyn_cast_or_null<Circle>(S);
106+
// expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Circle' fails}}
107+
// expected-note@-2 {{Assuming pointer value is null}}
108+
109+
if (const auto *T = dyn_cast_or_null<Triangle>(S)) {
110+
// expected-note@-1 {{Assuming dynamic cast from 'Shape' to 'Triangle' succeeds}}
111+
// expected-note@-2 {{'T' initialized here}}
112+
// expected-note@-3 {{'T' is non-null}}
113+
// expected-note@-4 {{Taking true branch}}
114+
115+
(void)(1 / !T);
116+
// expected-note@-1 {{'T' is non-null}}
117+
// expected-note@-2 {{Division by zero}}
118+
// expected-warning@-3 {{Division by zero}}
119+
// logic-warning@-4 {{Division by zero}}
120+
}
121+
}
122+
123+
void evalNullParamNullReturn(const Shape *S) {
124+
const auto *C = dyn_cast_or_null<Circle>(S);
125+
// expected-note@-1 {{Assuming null pointer is passed into cast}}
126+
// expected-note@-2 {{'C' initialized to a null pointer value}}
127+
128+
(void)(1 / (bool)C);
129+
// expected-note@-1 {{Division by zero}}
130+
// expected-warning@-2 {{Division by zero}}
131+
// logic-warning@-3 {{Division by zero}}
132+
}
133+
} // namespace test_dyn_cast_or_null

Diff for: ‎clang/test/Analysis/return-value-guaranteed.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %clang_analyze_cc1 \
2-
// RUN: -analyzer-checker=core,apiModeling.ReturnValue \
2+
// RUN: -analyzer-checker=core,apiModeling.llvm.ReturnValue \
33
// RUN: -analyzer-output=text -verify=class %s
44

55
struct Foo { int Field; };

0 commit comments

Comments
 (0)
Please sign in to comment.