Skip to content

Commit a017ed0

Browse files
authored
[analyzer] Model overflow builtins (#102602)
Add basic support for `builtin_*_overflow` primitives. These helps a lot for checking custom calloc-like functions with inlinable body. Without such support code like ```c #include <stddef.h> #include <stdlib.h> static void *myMalloc(size_t a1, size_t a2) { size_t res; if (__builtin_mul_overflow(a1, a2, &res)) return NULL; return malloc(res); } void test(void) { char *ptr = myMalloc(10, 1); ptr[20] = 10; } ```` does not trigger any warnings.
1 parent 94cf80d commit a017ed0

File tree

6 files changed

+421
-2
lines changed

6 files changed

+421
-2
lines changed

clang/docs/ReleaseNotes.rst

+2
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,8 @@ Static Analyzer
628628
New features
629629
^^^^^^^^^^^^
630630

631+
- Now CSA models `__builtin_*_overflow` functions. (#GH102602)
632+
631633
- MallocChecker now checks for ``ownership_returns(class, idx)`` and ``ownership_takes(class, idx)``
632634
attributes with class names different from "malloc". Clang static analyzer now reports an error
633635
if class of allocation and deallocation function mismatches.

clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp

+201-2
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,93 @@
1616

1717
#include "clang/Basic/Builtins.h"
1818
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
19+
#include "clang/StaticAnalyzer/Checkers/Taint.h"
1920
#include "clang/StaticAnalyzer/Core/Checker.h"
2021
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
2122
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
2223
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
2324
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
25+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
2426
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
27+
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
2528

2629
using namespace clang;
2730
using namespace ento;
31+
using namespace taint;
2832

2933
namespace {
3034

35+
QualType getSufficientTypeForOverflowOp(CheckerContext &C, const QualType &T) {
36+
// Calling a builtin with a non-integer type result produces compiler error.
37+
assert(T->isIntegerType());
38+
39+
ASTContext &ACtx = C.getASTContext();
40+
41+
unsigned BitWidth = ACtx.getIntWidth(T);
42+
return ACtx.getIntTypeForBitwidth(BitWidth * 2, T->isSignedIntegerType());
43+
}
44+
45+
QualType getOverflowBuiltinResultType(const CallEvent &Call) {
46+
// Calling a builtin with an incorrect argument count produces compiler error.
47+
assert(Call.getNumArgs() == 3);
48+
49+
return Call.getArgExpr(2)->getType()->getPointeeType();
50+
}
51+
52+
QualType getOverflowBuiltinResultType(const CallEvent &Call, CheckerContext &C,
53+
unsigned BI) {
54+
// Calling a builtin with an incorrect argument count produces compiler error.
55+
assert(Call.getNumArgs() == 3);
56+
57+
ASTContext &ACtx = C.getASTContext();
58+
59+
switch (BI) {
60+
case Builtin::BI__builtin_smul_overflow:
61+
case Builtin::BI__builtin_ssub_overflow:
62+
case Builtin::BI__builtin_sadd_overflow:
63+
return ACtx.IntTy;
64+
case Builtin::BI__builtin_smull_overflow:
65+
case Builtin::BI__builtin_ssubl_overflow:
66+
case Builtin::BI__builtin_saddl_overflow:
67+
return ACtx.LongTy;
68+
case Builtin::BI__builtin_smulll_overflow:
69+
case Builtin::BI__builtin_ssubll_overflow:
70+
case Builtin::BI__builtin_saddll_overflow:
71+
return ACtx.LongLongTy;
72+
case Builtin::BI__builtin_umul_overflow:
73+
case Builtin::BI__builtin_usub_overflow:
74+
case Builtin::BI__builtin_uadd_overflow:
75+
return ACtx.UnsignedIntTy;
76+
case Builtin::BI__builtin_umull_overflow:
77+
case Builtin::BI__builtin_usubl_overflow:
78+
case Builtin::BI__builtin_uaddl_overflow:
79+
return ACtx.UnsignedLongTy;
80+
case Builtin::BI__builtin_umulll_overflow:
81+
case Builtin::BI__builtin_usubll_overflow:
82+
case Builtin::BI__builtin_uaddll_overflow:
83+
return ACtx.UnsignedLongLongTy;
84+
case Builtin::BI__builtin_mul_overflow:
85+
case Builtin::BI__builtin_sub_overflow:
86+
case Builtin::BI__builtin_add_overflow:
87+
return getOverflowBuiltinResultType(Call);
88+
default:
89+
assert(false && "Unknown overflow builtin");
90+
return ACtx.IntTy;
91+
}
92+
}
93+
3194
class BuiltinFunctionChecker : public Checker<eval::Call> {
3295
public:
3396
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
97+
void handleOverflowBuiltin(const CallEvent &Call, CheckerContext &C,
98+
BinaryOperator::Opcode Op,
99+
QualType ResultType) const;
100+
const NoteTag *createBuiltinNoOverflowNoteTag(CheckerContext &C,
101+
bool BothFeasible, SVal Arg1,
102+
SVal Arg2, SVal Result) const;
103+
const NoteTag *createBuiltinOverflowNoteTag(CheckerContext &C) const;
104+
std::pair<bool, bool> checkOverflow(CheckerContext &C, SVal RetVal,
105+
QualType Res) const;
34106

35107
private:
36108
// From: clang/include/clang/Basic/Builtins.def
@@ -50,6 +122,102 @@ class BuiltinFunctionChecker : public Checker<eval::Call> {
50122

51123
} // namespace
52124

125+
const NoteTag *BuiltinFunctionChecker::createBuiltinNoOverflowNoteTag(
126+
CheckerContext &C, bool BothFeasible, SVal Arg1, SVal Arg2,
127+
SVal Result) const {
128+
return C.getNoteTag([Result, Arg1, Arg2, BothFeasible](
129+
PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
130+
if (!BR.isInteresting(Result))
131+
return;
132+
133+
// Propagate interestingness to input argumets if result is interesting.
134+
BR.markInteresting(Arg1);
135+
BR.markInteresting(Arg2);
136+
137+
if (BothFeasible)
138+
OS << "Assuming no overflow";
139+
});
140+
}
141+
142+
const NoteTag *
143+
BuiltinFunctionChecker::createBuiltinOverflowNoteTag(CheckerContext &C) const {
144+
return C.getNoteTag([](PathSensitiveBugReport &BR,
145+
llvm::raw_ostream &OS) { OS << "Assuming overflow"; },
146+
/*isPrunable=*/true);
147+
}
148+
149+
std::pair<bool, bool>
150+
BuiltinFunctionChecker::checkOverflow(CheckerContext &C, SVal RetVal,
151+
QualType Res) const {
152+
// Calling a builtin with a non-integer type result produces compiler error.
153+
assert(Res->isIntegerType());
154+
155+
unsigned BitWidth = C.getASTContext().getIntWidth(Res);
156+
bool IsUnsigned = Res->isUnsignedIntegerType();
157+
158+
auto MinValType = llvm::APSInt::getMinValue(BitWidth, IsUnsigned);
159+
auto MaxValType = llvm::APSInt::getMaxValue(BitWidth, IsUnsigned);
160+
nonloc::ConcreteInt MinVal{MinValType};
161+
nonloc::ConcreteInt MaxVal{MaxValType};
162+
163+
SValBuilder &SVB = C.getSValBuilder();
164+
ProgramStateRef State = C.getState();
165+
SVal IsLeMax = SVB.evalBinOp(State, BO_LE, RetVal, MaxVal, Res);
166+
SVal IsGeMin = SVB.evalBinOp(State, BO_GE, RetVal, MinVal, Res);
167+
168+
auto [MayNotOverflow, MayOverflow] =
169+
State->assume(IsLeMax.castAs<DefinedOrUnknownSVal>());
170+
auto [MayNotUnderflow, MayUnderflow] =
171+
State->assume(IsGeMin.castAs<DefinedOrUnknownSVal>());
172+
173+
return {MayOverflow || MayUnderflow, MayNotOverflow && MayNotUnderflow};
174+
}
175+
176+
void BuiltinFunctionChecker::handleOverflowBuiltin(const CallEvent &Call,
177+
CheckerContext &C,
178+
BinaryOperator::Opcode Op,
179+
QualType ResultType) const {
180+
// Calling a builtin with an incorrect argument count produces compiler error.
181+
assert(Call.getNumArgs() == 3);
182+
183+
ProgramStateRef State = C.getState();
184+
SValBuilder &SVB = C.getSValBuilder();
185+
const Expr *CE = Call.getOriginExpr();
186+
187+
SVal Arg1 = Call.getArgSVal(0);
188+
SVal Arg2 = Call.getArgSVal(1);
189+
190+
SVal RetValMax = SVB.evalBinOp(State, Op, Arg1, Arg2,
191+
getSufficientTypeForOverflowOp(C, ResultType));
192+
SVal RetVal = SVB.evalBinOp(State, Op, Arg1, Arg2, ResultType);
193+
194+
auto [Overflow, NotOverflow] = checkOverflow(C, RetValMax, ResultType);
195+
if (NotOverflow) {
196+
ProgramStateRef StateNoOverflow =
197+
State->BindExpr(CE, C.getLocationContext(), SVB.makeTruthVal(false));
198+
199+
if (auto L = Call.getArgSVal(2).getAs<Loc>()) {
200+
StateNoOverflow =
201+
StateNoOverflow->bindLoc(*L, RetVal, C.getLocationContext());
202+
203+
// Propagate taint if any of the argumets were tainted
204+
if (isTainted(State, Arg1) || isTainted(State, Arg2))
205+
StateNoOverflow = addTaint(StateNoOverflow, *L);
206+
}
207+
208+
C.addTransition(
209+
StateNoOverflow,
210+
createBuiltinNoOverflowNoteTag(
211+
C, /*BothFeasible=*/NotOverflow && Overflow, Arg1, Arg2, RetVal));
212+
}
213+
214+
if (Overflow) {
215+
C.addTransition(
216+
State->BindExpr(CE, C.getLocationContext(), SVB.makeTruthVal(true)),
217+
createBuiltinOverflowNoteTag(C));
218+
}
219+
}
220+
53221
bool BuiltinFunctionChecker::isBuiltinLikeFunction(
54222
const CallEvent &Call) const {
55223
const auto *FD = llvm::dyn_cast_or_null<FunctionDecl>(Call.getDecl());
@@ -82,10 +250,41 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call,
82250
return true;
83251
}
84252

85-
switch (FD->getBuiltinID()) {
253+
unsigned BI = FD->getBuiltinID();
254+
255+
switch (BI) {
86256
default:
87257
return false;
88-
258+
case Builtin::BI__builtin_mul_overflow:
259+
case Builtin::BI__builtin_smul_overflow:
260+
case Builtin::BI__builtin_smull_overflow:
261+
case Builtin::BI__builtin_smulll_overflow:
262+
case Builtin::BI__builtin_umul_overflow:
263+
case Builtin::BI__builtin_umull_overflow:
264+
case Builtin::BI__builtin_umulll_overflow:
265+
handleOverflowBuiltin(Call, C, BO_Mul,
266+
getOverflowBuiltinResultType(Call, C, BI));
267+
return true;
268+
case Builtin::BI__builtin_sub_overflow:
269+
case Builtin::BI__builtin_ssub_overflow:
270+
case Builtin::BI__builtin_ssubl_overflow:
271+
case Builtin::BI__builtin_ssubll_overflow:
272+
case Builtin::BI__builtin_usub_overflow:
273+
case Builtin::BI__builtin_usubl_overflow:
274+
case Builtin::BI__builtin_usubll_overflow:
275+
handleOverflowBuiltin(Call, C, BO_Sub,
276+
getOverflowBuiltinResultType(Call, C, BI));
277+
return true;
278+
case Builtin::BI__builtin_add_overflow:
279+
case Builtin::BI__builtin_sadd_overflow:
280+
case Builtin::BI__builtin_saddl_overflow:
281+
case Builtin::BI__builtin_saddll_overflow:
282+
case Builtin::BI__builtin_uadd_overflow:
283+
case Builtin::BI__builtin_uaddl_overflow:
284+
case Builtin::BI__builtin_uaddll_overflow:
285+
handleOverflowBuiltin(Call, C, BO_Add,
286+
getOverflowBuiltinResultType(Call, C, BI));
287+
return true;
89288
case Builtin::BI__builtin_assume:
90289
case Builtin::BI__assume: {
91290
assert (Call.getNumArgs() > 0);

0 commit comments

Comments
 (0)