aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
diff options
context:
space:
mode:
authorBalázs Kéri <1.int32@gmail.com>2023-04-12 09:33:51 +0200
committerBalázs Kéri <1.int32@gmail.com>2023-04-12 10:24:55 +0200
commitce1fb03db8174ca63fedc6e3aebdd6fb2c4fcfdf (patch)
treecc7646ae79b7240f01ce24240d05723a5c599eb9 /clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
parentac02bf666246a92801107fe2107b16a708955517 (diff)
downloadllvm-ce1fb03db8174ca63fedc6e3aebdd6fb2c4fcfdf.zip
llvm-ce1fb03db8174ca63fedc6e3aebdd6fb2c4fcfdf.tar.gz
llvm-ce1fb03db8174ca63fedc6e3aebdd6fb2c4fcfdf.tar.bz2
[clang][analyzer] Improve bug reports of StdLibraryFunctionsChecker.
Add an additional explanation of what is wrong if a constraint is not satisfied, in some cases. Additionally the bug report generation is changed to use raw_ostream. Reviewed By: Szelethus, NoQ Differential Revision: https://reviews.llvm.org/D144003
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp')
-rw-r--r--clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp336
1 files changed, 207 insertions, 129 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
index 98f5540..4e3ea09 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
@@ -102,21 +102,19 @@ class StdLibraryFunctionsChecker
/// Special argument number for specifying the return value.
static const ArgNo Ret;
- using DescString = SmallString<96>;
-
- /// Returns the string representation of an argument index.
+ /// Get a string representation of an argument index.
/// E.g.: (1) -> '1st arg', (2) - > '2nd arg'
- static SmallString<8> getArgDesc(ArgNo);
- /// Append textual description of a numeric range [RMin,RMax] to the string
+ static void printArgDesc(ArgNo, llvm::raw_ostream &Out);
+ /// Append textual description of a numeric range [RMin,RMax] to
/// \p Out.
static void appendInsideRangeDesc(llvm::APSInt RMin, llvm::APSInt RMax,
QualType ArgT, BasicValueFactory &BVF,
- DescString &Out);
- /// Append textual description of a numeric range out of [RMin,RMax] to the
- /// string \p Out.
+ llvm::raw_ostream &Out);
+ /// Append textual description of a numeric range out of [RMin,RMax] to
+ /// \p Out.
static void appendOutOfRangeDesc(llvm::APSInt RMin, llvm::APSInt RMax,
QualType ArgT, BasicValueFactory &BVF,
- DescString &Out);
+ llvm::raw_ostream &Out);
class ValueConstraint;
@@ -152,21 +150,25 @@ class StdLibraryFunctionsChecker
/// Represents that in which context do we require a description of the
/// constraint.
- enum class DescriptionKind {
- /// The constraint is violated.
+ enum DescriptionKind {
+ /// Describe a constraint that was violated.
+ /// Description should start with something like "should be".
Violation,
- /// We assume that the constraint is satisfied.
+ /// Describe a constraint that was assumed to be true.
/// This can be used when a precondition is satisfied, or when a summary
/// case is applied.
+ /// Description should start with something like "is".
Assumption
};
/// Give a description that explains the constraint to the user. Used when
/// a bug is reported or when the constraint is applied and displayed as a
- /// note.
- virtual std::string describe(DescriptionKind DK, const CallEvent &Call,
- ProgramStateRef State,
- const Summary &Summary) const {
+ /// note. The description should not mention the argument (getArgNo).
+ /// See StdLibraryFunctionsChecker::reportBug about how this function is
+ /// used (this function is used not only there).
+ virtual void describe(DescriptionKind DK, const CallEvent &Call,
+ ProgramStateRef State, const Summary &Summary,
+ llvm::raw_ostream &Out) const {
// There are some descendant classes that are not used as argument
// constraints, e.g. ComparisonConstraint. In that case we can safely
// ignore the implementation of this function.
@@ -174,6 +176,31 @@ class StdLibraryFunctionsChecker
"Description not implemented for summary case constraints");
}
+ /// Give a description that explains the actual argument value (where the
+ /// current ValueConstraint applies to) to the user. This function should be
+ /// called only when the current constraint is satisfied by the argument.
+ /// It should produce a more precise description than the constraint itself.
+ /// The actual value of the argument and the program state can be used to
+ /// make the description more precise. In the most simple case, if the
+ /// argument has a fixed known value this value can be printed into \p Out,
+ /// this is done by default.
+ /// The function should return true if a description was printed to \p Out,
+ /// otherwise false.
+ /// See StdLibraryFunctionsChecker::reportBug about how this function is
+ /// used.
+ virtual bool describeArgumentValue(const CallEvent &Call,
+ ProgramStateRef State,
+ const Summary &Summary,
+ llvm::raw_ostream &Out) const {
+ if (auto N = getArgSVal(Call, getArgNo()).getAs<NonLoc>()) {
+ if (const llvm::APSInt *Int = N->getAsInteger()) {
+ Out << *Int;
+ return true;
+ }
+ }
+ return false;
+ }
+
/// Return those arguments that should be tracked when we report a bug about
/// argument constraint violation. By default it is the argument that is
/// constrained, however, in some special cases we need to track other
@@ -254,9 +281,13 @@ class StdLibraryFunctionsChecker
const Summary &Summary,
CheckerContext &C) const override;
- std::string describe(DescriptionKind DK, const CallEvent &Call,
- ProgramStateRef State,
- const Summary &Summary) const override;
+ void describe(DescriptionKind DK, const CallEvent &Call,
+ ProgramStateRef State, const Summary &Summary,
+ llvm::raw_ostream &Out) const override;
+
+ bool describeArgumentValue(const CallEvent &Call, ProgramStateRef State,
+ const Summary &Summary,
+ llvm::raw_ostream &Out) const override;
ValueConstraintPtr negate() const override {
RangeConstraint Tmp(*this);
@@ -342,9 +373,13 @@ class StdLibraryFunctionsChecker
const Summary &Summary,
CheckerContext &C) const override;
- std::string describe(DescriptionKind DK, const CallEvent &Call,
- ProgramStateRef State,
- const Summary &Summary) const override;
+ void describe(DescriptionKind DK, const CallEvent &Call,
+ ProgramStateRef State, const Summary &Summary,
+ llvm::raw_ostream &Out) const override;
+
+ bool describeArgumentValue(const CallEvent &Call, ProgramStateRef State,
+ const Summary &Summary,
+ llvm::raw_ostream &Out) const override;
ValueConstraintPtr negate() const override {
NotNullConstraint Tmp(*this);
@@ -396,9 +431,9 @@ class StdLibraryFunctionsChecker
const Summary &Summary,
CheckerContext &C) const override;
- std::string describe(DescriptionKind DK, const CallEvent &Call,
- ProgramStateRef State,
- const Summary &Summary) const override;
+ void describe(DescriptionKind DK, const CallEvent &Call,
+ ProgramStateRef State, const Summary &Summary,
+ llvm::raw_ostream &Out) const override;
std::vector<ArgNo> getArgsToTrack() const override {
std::vector<ArgNo> Result{ArgN};
@@ -773,8 +808,20 @@ private:
return;
assert(Call.getDecl() &&
"Function found in summary must have a declaration available");
- std::string Msg = VC->describe(ValueConstraint::DescriptionKind::Violation,
- Call, C.getState(), Summary);
+ SmallString<256> Msg;
+ llvm::raw_svector_ostream MsgOs(Msg);
+
+ MsgOs << "The ";
+ printArgDesc(VC->getArgNo(), MsgOs);
+ MsgOs << " to '" << getFunctionName(Call) << "' ";
+ bool ValuesPrinted =
+ NegatedVC->describeArgumentValue(Call, N->getState(), Summary, MsgOs);
+ if (ValuesPrinted)
+ MsgOs << " but ";
+ else
+ MsgOs << "is out of the accepted range; It ";
+ VC->describe(ValueConstraint::Violation, Call, C.getState(), Summary,
+ MsgOs);
Msg[0] = toupper(Msg[0]);
if (!BT_InvalidArg)
BT_InvalidArg = std::make_unique<BugType>(
@@ -816,55 +863,37 @@ static BasicValueFactory &getBVF(ProgramStateRef State) {
} // end of anonymous namespace
-SmallString<8>
-StdLibraryFunctionsChecker::getArgDesc(StdLibraryFunctionsChecker::ArgNo ArgN) {
- SmallString<8> Result;
- Result += std::to_string(ArgN + 1);
- Result += llvm::getOrdinalSuffix(ArgN + 1);
- Result += " argument";
- return Result;
+void StdLibraryFunctionsChecker::printArgDesc(
+ StdLibraryFunctionsChecker::ArgNo ArgN, llvm::raw_ostream &Out) {
+ Out << std::to_string(ArgN + 1);
+ Out << llvm::getOrdinalSuffix(ArgN + 1);
+ Out << " argument";
}
void StdLibraryFunctionsChecker::appendInsideRangeDesc(llvm::APSInt RMin,
llvm::APSInt RMax,
QualType ArgT,
BasicValueFactory &BVF,
- DescString &Out) {
+ llvm::raw_ostream &Out) {
if (RMin.isZero() && RMax.isZero())
- Out.append("zero");
+ Out << "zero";
else if (RMin == RMax)
- RMin.toString(Out);
+ Out << RMin;
else if (RMin == BVF.getMinValue(ArgT)) {
if (RMax == -1)
- Out.append("< 0");
- else {
- Out.append("<= ");
- RMax.toString(Out);
- }
+ Out << "< 0";
+ else
+ Out << "<= " << RMax;
} else if (RMax == BVF.getMaxValue(ArgT)) {
if (RMin.isOne())
- Out.append("> 0");
- else {
- Out.append(">= ");
- RMin.toString(Out);
- }
+ Out << "> 0";
+ else
+ Out << ">= " << RMin;
} else if (RMin.isNegative() == RMax.isNegative() &&
RMin.getLimitedValue() == RMax.getLimitedValue() - 1) {
- RMin.toString(Out);
- Out.append(" or ");
- RMax.toString(Out);
- } else if (RMin.isNegative() == RMax.isNegative() &&
- RMin.getLimitedValue() == RMax.getLimitedValue() - 2) {
- RMin.toString(Out);
- Out.append(", ");
- (RMin + 1).toString(Out, 10, RMin.isSigned());
- Out.append(" or ");
- RMax.toString(Out);
+ Out << RMin << " or " << RMax;
} else {
- Out.append("between ");
- RMin.toString(Out);
- Out.append(" and ");
- RMax.toString(Out);
+ Out << "between " << RMin << " and " << RMax;
}
}
@@ -872,37 +901,26 @@ void StdLibraryFunctionsChecker::appendOutOfRangeDesc(llvm::APSInt RMin,
llvm::APSInt RMax,
QualType ArgT,
BasicValueFactory &BVF,
- DescString &Out) {
+ llvm::raw_ostream &Out) {
if (RMin.isZero() && RMax.isZero())
- Out.append("nonzero");
+ Out << "nonzero";
else if (RMin == RMax) {
- Out.append("not equal to ");
- RMin.toString(Out);
+ Out << "not equal to " << RMin;
} else if (RMin == BVF.getMinValue(ArgT)) {
if (RMax == -1)
- Out.append(">= 0");
- else {
- Out.append("> ");
- RMax.toString(Out);
- }
+ Out << ">= 0";
+ else
+ Out << "> " << RMax;
} else if (RMax == BVF.getMaxValue(ArgT)) {
if (RMin.isOne())
- Out.append("<= 0");
- else {
- Out.append("< ");
- RMin.toString(Out);
- }
+ Out << "<= 0";
+ else
+ Out << "< " << RMin;
} else if (RMin.isNegative() == RMax.isNegative() &&
RMin.getLimitedValue() == RMax.getLimitedValue() - 1) {
- Out.append("not ");
- RMin.toString(Out);
- Out.append(" and not ");
- RMax.toString(Out);
+ Out << "not " << RMin << " and not " << RMax;
} else {
- Out.append("not between ");
- RMin.toString(Out);
- Out.append(" and ");
- RMax.toString(Out);
+ Out << "not between " << RMin << " and " << RMax;
}
}
@@ -981,42 +999,77 @@ ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::apply(
return State;
}
-std::string StdLibraryFunctionsChecker::RangeConstraint::describe(
+void StdLibraryFunctionsChecker::RangeConstraint::describe(
DescriptionKind DK, const CallEvent &Call, ProgramStateRef State,
- const Summary &Summary) const {
+ const Summary &Summary, llvm::raw_ostream &Out) const {
BasicValueFactory &BVF = getBVF(State);
QualType T = Summary.getArgType(getArgNo());
- DescString Result;
- const auto Violation = ValueConstraint::DescriptionKind::Violation;
-
- Result += "the ";
- Result += getArgDesc(ArgN);
- Result += " to '";
- Result += getFunctionName(Call);
- Result += DK == Violation ? "' should be " : "' is ";
+
+ Out << ((DK == Violation) ? "should be " : "is ");
if (!Description.empty()) {
- Result += Description;
+ Out << Description;
} else {
unsigned I = Ranges.size();
if (Kind == WithinRange) {
for (const std::pair<RangeInt, RangeInt> &R : Ranges) {
appendInsideRangeDesc(BVF.getValue(R.first, T),
- BVF.getValue(R.second, T), T, BVF, Result);
+ BVF.getValue(R.second, T), T, BVF, Out);
if (--I > 0)
- Result += " or ";
+ Out << " or ";
}
} else {
for (const std::pair<RangeInt, RangeInt> &R : Ranges) {
appendOutOfRangeDesc(BVF.getValue(R.first, T),
- BVF.getValue(R.second, T), T, BVF, Result);
+ BVF.getValue(R.second, T), T, BVF, Out);
if (--I > 0)
- Result += " and ";
+ Out << " and ";
}
}
}
+}
+
+bool StdLibraryFunctionsChecker::RangeConstraint::describeArgumentValue(
+ const CallEvent &Call, ProgramStateRef State, const Summary &Summary,
+ llvm::raw_ostream &Out) const {
+ unsigned int NRanges = 0;
+ bool HaveAllRanges = true;
+
+ ProgramStateManager &Mgr = State->getStateManager();
+ BasicValueFactory &BVF = Mgr.getSValBuilder().getBasicValueFactory();
+ ConstraintManager &CM = Mgr.getConstraintManager();
+ SVal V = getArgSVal(Call, getArgNo());
- return Result.c_str();
+ if (auto N = V.getAs<NonLoc>()) {
+ if (const llvm::APSInt *Int = N->getAsInteger()) {
+ Out << "is ";
+ Out << *Int;
+ return true;
+ }
+ QualType T = Summary.getArgType(getArgNo());
+ SmallString<128> MoreInfo;
+ llvm::raw_svector_ostream MoreInfoOs(MoreInfo);
+ auto ApplyF = [&](const llvm::APSInt &Min, const llvm::APSInt &Max) {
+ if (CM.assumeInclusiveRange(State, *N, Min, Max, true)) {
+ if (NRanges > 0)
+ MoreInfoOs << " or ";
+ appendInsideRangeDesc(Min, Max, T, BVF, MoreInfoOs);
+ ++NRanges;
+ } else {
+ HaveAllRanges = false;
+ }
+ return true;
+ };
+
+ applyOnRange(Kind, BVF, T, ApplyF);
+ assert(NRanges > 0);
+ if (!HaveAllRanges || NRanges == 1) {
+ Out << "is ";
+ Out << MoreInfo;
+ return true;
+ }
+ }
+ return false;
}
ProgramStateRef StdLibraryFunctionsChecker::ComparisonConstraint::apply(
@@ -1055,17 +1108,23 @@ ProgramStateRef StdLibraryFunctionsChecker::NotNullConstraint::apply(
return State->assume(L, CannotBeNull);
}
-std::string StdLibraryFunctionsChecker::NotNullConstraint::describe(
+void StdLibraryFunctionsChecker::NotNullConstraint::describe(
DescriptionKind DK, const CallEvent &Call, ProgramStateRef State,
- const Summary &Summary) const {
- SmallString<48> Result;
- const auto Violation = ValueConstraint::DescriptionKind::Violation;
- Result += "the ";
- Result += getArgDesc(ArgN);
- Result += " to '";
- Result += getFunctionName(Call);
- Result += DK == Violation ? "' should not be NULL" : "' is not NULL";
- return Result.c_str();
+ const Summary &Summary, llvm::raw_ostream &Out) const {
+ assert(CannotBeNull &&
+ "Describe should not be used when the value must be NULL");
+ if (DK == Violation)
+ Out << "should not be NULL";
+ else
+ Out << "is not NULL";
+}
+
+bool StdLibraryFunctionsChecker::NotNullConstraint::describeArgumentValue(
+ const CallEvent &Call, ProgramStateRef State, const Summary &Summary,
+ llvm::raw_ostream &Out) const {
+ assert(!CannotBeNull && "This function is used when the value is NULL");
+ Out << "is NULL";
+ return true;
}
ProgramStateRef StdLibraryFunctionsChecker::BufferSizeConstraint::apply(
@@ -1110,28 +1169,21 @@ ProgramStateRef StdLibraryFunctionsChecker::BufferSizeConstraint::apply(
llvm_unreachable("Size argument or the dynamic size is Undefined");
}
-std::string StdLibraryFunctionsChecker::BufferSizeConstraint::describe(
+void StdLibraryFunctionsChecker::BufferSizeConstraint::describe(
DescriptionKind DK, const CallEvent &Call, ProgramStateRef State,
- const Summary &Summary) const {
- SmallString<96> Result;
- const auto Violation = ValueConstraint::DescriptionKind::Violation;
- Result += "the size of the ";
- Result += getArgDesc(ArgN);
- Result += " to '";
- Result += getFunctionName(Call);
- Result += DK == Violation ? "' should be " : "' is ";
- Result += "equal to or greater than ";
+ const Summary &Summary, llvm::raw_ostream &Out) const {
+ Out << ((DK == Violation) ? "should be " : "is ");
+ Out << "a buffer with size equal to or greater than ";
if (ConcreteSize) {
- ConcreteSize->toString(Result);
+ Out << *ConcreteSize;
} else if (SizeArgN) {
- Result += "the value of the ";
- Result += getArgDesc(*SizeArgN);
+ Out << "the value of the ";
+ printArgDesc(*SizeArgN, Out);
if (SizeMultiplierArgN) {
- Result += " times the ";
- Result += getArgDesc(*SizeMultiplierArgN);
+ Out << " times the ";
+ printArgDesc(*SizeMultiplierArgN, Out);
}
}
- return Result.c_str();
}
void StdLibraryFunctionsChecker::checkPreCall(const CallEvent &Call,
@@ -1164,10 +1216,15 @@ void StdLibraryFunctionsChecker::checkPreCall(const CallEvent &Call,
assert(SuccessSt);
NewState = SuccessSt;
if (NewState != State) {
- SmallString<64> Msg;
- Msg += "Assuming ";
- Msg += Constraint->describe(ValueConstraint::DescriptionKind::Assumption,
- Call, NewState, Summary);
+ SmallString<128> Msg;
+ llvm::raw_svector_ostream Os(Msg);
+ Os << "Assuming that the ";
+ printArgDesc(Constraint->getArgNo(), Os);
+ Os << " to '";
+ Os << getFunctionName(Call);
+ Os << "' ";
+ Constraint->describe(ValueConstraint::Assumption, Call, NewState, Summary,
+ Os);
const auto ArgSVal = Call.getArgSVal(Constraint->getArgNo());
NewNode = C.addTransition(
NewState, NewNode,
@@ -3471,6 +3528,27 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
ErrnoIrrelevant, "Function returns 0")
.Case({ReturnValueCondition(WithinRange, SingleValue(1))},
ErrnoIrrelevant, "Function returns 1"));
+ addToFunctionSummaryMap(
+ "__test_case_range_1_2__4_6",
+ Signature(ArgTypes{IntTy}, RetType{IntTy}),
+ Summary(EvalCallAsPure)
+ .Case({ArgumentCondition(0U, WithinRange,
+ IntRangeVector{{IntMin, 0}, {3, 3}}),
+ ReturnValueCondition(WithinRange, SingleValue(1))},
+ ErrnoIrrelevant)
+ .Case({ArgumentCondition(0U, WithinRange,
+ IntRangeVector{{3, 3}, {7, IntMax}}),
+ ReturnValueCondition(WithinRange, SingleValue(2))},
+ ErrnoIrrelevant)
+ .Case({ArgumentCondition(0U, WithinRange,
+ IntRangeVector{{IntMin, 0}, {7, IntMax}}),
+ ReturnValueCondition(WithinRange, SingleValue(3))},
+ ErrnoIrrelevant)
+ .Case({ArgumentCondition(
+ 0U, WithinRange,
+ IntRangeVector{{IntMin, 0}, {3, 3}, {7, IntMax}}),
+ ReturnValueCondition(WithinRange, SingleValue(4))},
+ ErrnoIrrelevant));
}
SummariesInitialized = true;