clang 24.0.0git
BoundsChecking.cpp
Go to the documentation of this file.
1//===- BoundsChecking.cpp - Bounds checking related APIs --------*- 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 file implements 'checkBounds', a function that compares memory offsets
10// (that may be symbolic) and uses heuristical workarounds to provide more
11// accurate results than the 'naive' evalBinOp calls.
12//
13//===----------------------------------------------------------------------===//
14
16
17using namespace clang;
18using namespace ento;
19
20// NOTE: This function is the "heart" of this algorithm. It simplifies
21// inequalities with transformations that are valid (and very elementary) in
22// pure mathematics, but become invalid if we use them in C++ number model
23// where the calculations may overflow.
24// Due to the overflow issues I think it's impossible (or at least not
25// practical) to integrate this kind of simplification into the resolution of
26// arbitrary inequalities (i.e. the code of `evalBinOp`); but this function
27// produces valid results when the calculations are handling memory offsets
28// and every value is well below SIZE_MAX.
29// NOTE: the simplification preserves the order of the two operands in a
30// mathematical sense, but it may change the result produced by a C++
31// comparison operator (and the automatic type conversions).
32// For example, consider a comparison "X+1 < 0", where the LHS is stored as a
33// size_t and the RHS is stored in an int. (As size_t is unsigned, this
34// comparison is false for all values of "X".) However, the simplification may
35// turn it into "X < -1", which is still always false in a mathematical sense,
36// but can produce a true result when evaluated by `evalBinOp` (which follows
37// the rules of C++ and casts -1 to SIZE_MAX).
38static std::pair<NonLoc, nonloc::ConcreteInt>
40 SValBuilder &svalBuilder) {
41 const llvm::APSInt &extentVal = extent.getValue();
42 std::optional<nonloc::SymbolVal> SymVal = offset.getAs<nonloc::SymbolVal>();
43 if (SymVal && SymVal->isExpression()) {
44 if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(SymVal->getSymbol())) {
45 llvm::APSInt constant = APSIntType(extentVal).convert(SIE->getRHS());
46 switch (SIE->getOpcode()) {
47 case BO_Mul:
48 // The constant should never be 0 here, becasue multiplication by zero
49 // is simplified by the engine.
50 if ((extentVal % constant) != 0)
51 return std::pair<NonLoc, nonloc::ConcreteInt>(offset, extent);
52 else
54 nonloc::SymbolVal(SIE->getLHS()),
55 svalBuilder.makeIntVal(extentVal / constant), svalBuilder);
56 case BO_Add:
58 nonloc::SymbolVal(SIE->getLHS()),
59 svalBuilder.makeIntVal(extentVal - constant), svalBuilder);
60 default:
61 break;
62 }
63 }
64 }
65
66 return std::pair<NonLoc, nonloc::ConcreteInt>(offset, extent);
67}
68
70 const llvm::APSInt *MaxV = SVB.getMaxValue(State, Value);
71 return MaxV && MaxV->isNegative();
72}
73
74static bool isUnsigned(SValBuilder &SVB, NonLoc Value) {
76 return T->isUnsignedIntegerType();
77}
78
79// Evaluate the comparison Value < Threshold with the help of the custom
80// simplification algorithm defined for this checker. Return a pair of states,
81// where the first one corresponds to "value below threshold" and the second
82// corresponds to "value at or above threshold". Returns {nullptr, nullptr} in
83// the case when the evaluation fails.
84// If the optional argument CheckEquality is true, then use BO_EQ instead of
85// the default BO_LT after consistently applying the same simplification steps.
86static std::pair<ProgramStateRef, ProgramStateRef>
88 SValBuilder &SVB, bool CheckEquality = false) {
89 if (auto ConcreteThreshold = Threshold.getAs<nonloc::ConcreteInt>()) {
90 std::tie(Value, Threshold) =
91 getSimplifiedOffsets(Value, *ConcreteThreshold, SVB);
92 }
93
94 // We want to perform a _mathematical_ comparison between the numbers `Value`
95 // and `Threshold`; but `evalBinOpNN` evaluates a C/C++ operator that may
96 // perform automatic conversions. For example the number -1 is less than the
97 // number 1000, but -1 < `1000ull` will evaluate to `false` because the `int`
98 // -1 is converted to ULONGLONG_MAX.
99 // To avoid automatic conversions, we evaluate the "obvious" cases without
100 // calling `evalBinOpNN`:
101 if (isNegative(SVB, State, Value) && isUnsigned(SVB, Threshold)) {
102 if (CheckEquality) {
103 // negative_value == unsigned_threshold is always false
104 return {nullptr, State};
105 }
106 // negative_value < unsigned_threshold is always true
107 return {State, nullptr};
108 }
109 if (isUnsigned(SVB, Value) && isNegative(SVB, State, Threshold)) {
110 // unsigned_value == negative_threshold and
111 // unsigned_value < negative_threshold are both always false
112 return {nullptr, State};
113 }
114 // FIXME: These special cases are sufficient for handling real-world
115 // comparisons, but in theory there could be contrived situations where
116 // automatic conversion of a symbolic value (which can be negative and can be
117 // positive) leads to incorrect results.
118 // NOTE: We NEED to use the `evalBinOpNN` call in the "common" case, because
119 // we want to ensure that assumptions coming from this precondition and
120 // assumptions coming from regular C/C++ operator calls are represented by
121 // constraints on the same symbolic expression. A solution that would
122 // evaluate these "mathematical" comparisons through a separate pathway would
123 // be a step backwards in this sense.
124
125 const BinaryOperatorKind OpKind = CheckEquality ? BO_EQ : BO_LT;
126 auto BelowThreshold =
127 SVB.evalBinOpNN(State, OpKind, Value, Threshold, SVB.getConditionType())
128 .getAs<NonLoc>();
129
130 if (BelowThreshold)
131 return State->assume(*BelowThreshold);
132
133 return {nullptr, nullptr};
134}
135
137 NonLoc Offset,
138 std::optional<NonLoc> Extent,
139 bounds::CheckFlags Flags) {
140
141 bounds::CheckResult Res(Offset);
142
143 // CHECK LOWER BOUND
144 if (Flags.CheckUnderflow) {
145 auto [PrecedesLowerBound, WithinLowerBound] =
146 compareValueToThreshold(State, Offset, SVB.makeZeroArrayIndex(), SVB);
147
148 if (PrecedesLowerBound) {
149 // The analyzer thinks that the offset may be invalid (negative)...
150 if (Flags.OffsetObviouslyNonnegative) {
151 // ...but the offset is obviously non-negative (clear array subscript
152 // with an unsigned index), so we're in a buggy situation.
153
154 // TODO: Currently the analyzer ignores many casts (e.g. signed ->
155 // unsigned casts), so it can easily reach states where it will load a
156 // signed (and negative) value from an unsigned variable. This sanity
157 // check is a duct tape "solution" that silences most of the ugly false
158 // positives that are caused by this buggy behavior. Note that this is
159 // not a complete solution: this cannot silence reports where pointer
160 // arithmetic complicates the picture and cannot ensure modeling of the
161 // "unsigned index is positive with highest bit set" cases which are
162 // "usurped" by the nonsense "unsigned index is negative" case.
163 // For more information about this topic, see the umbrella ticket
164 // https://github.com/llvm/llvm-project/issues/39492
165 // TODO: Remove this hack once 'SymbolCast's are modeled properly.
166
167 if (!WithinLowerBound) {
168 // The state is completely nonsense -- let's just sink it!
169 Res.IsCorruptedState = true;
170 return Res;
171 }
172 // Otherwise continue on the 'WithinLowerBound' branch where the
173 // unsigned index _is_ non-negative. Don't mention this assumption as a
174 // note tag, because it would just confuse the users!
175 } else {
176 Res.MayUnderflow = true;
177
178 if (!WithinLowerBound) {
179 // ...and it cannot be valid (>= 0), so report an error.
180 return Res;
181 }
182 }
183 }
184
185 // Actually update the state. The "if" only fails in the extremely unlikely
186 // case when compareValueToThreshold returns {nullptr, nullptr} because
187 // evalBinOpNN fails to evaluate the less-than operator.
188 if (WithinLowerBound)
189 State = WithinLowerBound;
190 }
191
192 // CHECK UPPER BOUND
193 if (Extent) {
194 // In a situation where both underflow and overflow are possible (but the
195 // index is either tainted or known to be invalid), the logic of this
196 // checker will first assume that the offset is non-negative, and then
197 // (with this additional assumption) it will detect an overflow error.
198 // In this situation the warning message should mention both possibilities.
199
200 auto [WithinUpperBound, ExceedsUpperBound] =
201 compareValueToThreshold(State, Offset, *Extent, SVB);
202
203 if (ExceedsUpperBound) {
204 // The offset may be invalid (>= Size)...
205 Res.ExtentIfMayOverflow = Extent;
206
207 if (!WithinUpperBound) {
208 // ...and it cannot be within bounds, so report an error, unless we can
209 // definitely determine that this is an idiomatic `&array[size]`
210 // expression that calculates the past-the-end pointer.
211 if (Flags.AcceptPastTheEnd) {
212 auto [EqualsToThreshold, NotEqualToThreshold] =
213 compareValueToThreshold(State, Offset, *Extent, SVB,
214 /*CheckEquality=*/true);
215 if (EqualsToThreshold && !NotEqualToThreshold) {
216 Res.ExtentIfMayOverflow = std::nullopt;
217 Res.InBoundsState = EqualsToThreshold;
218 }
219 }
220 return Res;
221 }
222 }
223 if (WithinUpperBound)
224 State = WithinUpperBound;
225 }
226
227 Res.InBoundsState = State;
228 return Res;
229}
static std::pair< ProgramStateRef, ProgramStateRef > compareValueToThreshold(ProgramStateRef State, NonLoc Value, NonLoc Threshold, SValBuilder &SVB, bool CheckEquality=false)
static bool isNegative(SValBuilder &SVB, ProgramStateRef State, NonLoc Value)
static bool isUnsigned(SValBuilder &SVB, NonLoc Value)
static std::pair< NonLoc, nonloc::ConcreteInt > getSimplifiedOffsets(NonLoc offset, nonloc::ConcreteInt extent, SValBuilder &svalBuilder)
A (possibly-)qualified type.
Definition TypeBase.h:938
QualType getType() const
Definition Value.cpp:238
A record of the "type" of an APSInt, used for conversions.
Definition APSIntType.h:19
llvm::APSInt convert(const llvm::APSInt &Value) const LLVM_READONLY
Convert and return a new APSInt with the given value, but this type's bit width and signedness.
Definition APSIntType.h:48
ASTContext & getContext()
nonloc::ConcreteInt makeIntVal(const IntegerLiteral *integer)
virtual SVal evalBinOpNN(ProgramStateRef state, BinaryOperator::Opcode op, NonLoc lhs, NonLoc rhs, QualType resultTy)=0
Create a new value which represents a binary expression with two non- location operands.
QualType getConditionType() const
virtual const llvm::APSInt * getMaxValue(ProgramStateRef state, SVal val)=0
Tries to get the maximal possible (integer) value of a given SVal.
std::optional< T > getAs() const
Convert to the specified SVal type, returning std::nullopt if this SVal is not of the desired type.
Definition SVals.h:88
Value representing integer constant.
Definition SVals.h:306
APSIntPtr getValue() const
Definition SVals.h:310
Represents symbolic expression that isn't a location.
Definition SVals.h:285
CheckResult checkBounds(ProgramStateRef State, SValBuilder &SVB, NonLoc Offset, std::optional< NonLoc > Extent, CheckFlags Flags)
Checks the validity of accessing a memory region with extent Extent at offset Offset.
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
BinarySymExprImpl< const SymExpr *, APSIntPtr, SymExpr::Kind::SymIntExprKind > SymIntExpr
Represents a symbolic expression like 'x' + 3.
The JSON file list parser is used to communicate input to InstallAPI.
const FunctionProtoType * T