Skip to content

Commit 8a50337

Browse files
authored
[NFC][analyzer] Conversion to CheckerFamily: CStringChecker (#150971)
This commit converts the class CStringChecker to the checker family framework and slightly simplifies the implementation. This commit is NFC and preserves the confused garbage descriptions and categories of the bug types (which was inherited from old mistakes). I'm planning to clean that up in a follow-up commit.
1 parent 56944e6 commit 8a50337

File tree

1 file changed

+52
-94
lines changed

1 file changed

+52
-94
lines changed

clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp

Lines changed: 52 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -78,35 +78,30 @@ static QualType getCharPtrType(ASTContext &Ctx, CharKind CK) {
7878
: Ctx.WideCharTy);
7979
}
8080

81-
class CStringChecker : public Checker< eval::Call,
82-
check::PreStmt<DeclStmt>,
83-
check::LiveSymbols,
84-
check::DeadSymbols,
85-
check::RegionChanges
86-
> {
87-
mutable std::unique_ptr<BugType> BT_Null, BT_Bounds, BT_Overlap,
88-
BT_NotCString, BT_AdditionOverflow, BT_UninitRead;
89-
81+
class CStringChecker
82+
: public CheckerFamily<eval::Call, check::PreStmt<DeclStmt>,
83+
check::LiveSymbols, check::DeadSymbols,
84+
check::RegionChanges> {
9085
mutable const char *CurrentFunctionDescription = nullptr;
9186

9287
public:
93-
/// The filter is used to filter out the diagnostics which are not enabled by
94-
/// the user.
95-
struct CStringChecksFilter {
96-
bool CheckCStringNullArg = false;
97-
bool CheckCStringOutOfBounds = false;
98-
bool CheckCStringBufferOverlap = false;
99-
bool CheckCStringNotNullTerm = false;
100-
bool CheckCStringUninitializedRead = false;
101-
102-
CheckerNameRef CheckNameCStringNullArg;
103-
CheckerNameRef CheckNameCStringOutOfBounds;
104-
CheckerNameRef CheckNameCStringBufferOverlap;
105-
CheckerNameRef CheckNameCStringNotNullTerm;
106-
CheckerNameRef CheckNameCStringUninitializedRead;
107-
};
108-
109-
CStringChecksFilter Filter;
88+
// FIXME: The bug types emitted by this checker family have confused garbage
89+
// in their Description and Category fields (e.g. `categories::UnixAPI` is
90+
// passed as the description in several cases and `uninitialized` is mistyped
91+
// as `unitialized`). This should be cleaned up.
92+
CheckerFrontendWithBugType NullArg{categories::UnixAPI};
93+
CheckerFrontendWithBugType OutOfBounds{"Out-of-bound array access"};
94+
CheckerFrontendWithBugType BufferOverlap{categories::UnixAPI,
95+
"Improper arguments"};
96+
CheckerFrontendWithBugType NotNullTerm{categories::UnixAPI};
97+
CheckerFrontendWithBugType UninitializedRead{
98+
"Accessing unitialized/garbage values"};
99+
100+
// FIXME: This bug type should be removed because it is only emitted in a
101+
// situation that is practically impossible.
102+
const BugType AdditionOverflow{&OutOfBounds, "API"};
103+
104+
StringRef getDebugTag() const override { return "MallocChecker"; }
110105

111106
static void *getTag() { static int tag; return &tag; }
112107

@@ -384,7 +379,7 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C,
384379
assumeZero(C, State, l, Arg.Expression->getType());
385380

386381
if (stateNull && !stateNonNull) {
387-
if (Filter.CheckCStringNullArg) {
382+
if (NullArg.isEnabled()) {
388383
SmallString<80> buf;
389384
llvm::raw_svector_ostream OS(buf);
390385
assert(CurrentFunctionDescription);
@@ -468,7 +463,7 @@ ProgramStateRef CStringChecker::checkInit(CheckerContext &C,
468463
return State;
469464

470465
// Ensure that we wouldn't read uninitialized value.
471-
if (Filter.CheckCStringUninitializedRead &&
466+
if (UninitializedRead.isEnabled() &&
472467
State->getSVal(*FirstElementVal).isUndef()) {
473468
llvm::SmallString<258> Buf;
474469
llvm::raw_svector_ostream OS(Buf);
@@ -524,7 +519,7 @@ ProgramStateRef CStringChecker::checkInit(CheckerContext &C,
524519
if (!isa<Loc>(LastElementVal))
525520
return State;
526521

527-
if (Filter.CheckCStringUninitializedRead &&
522+
if (UninitializedRead.isEnabled() &&
528523
State->getSVal(LastElementVal.castAs<Loc>()).isUndef()) {
529524
const llvm::APSInt *IdxInt = LastIdx.getAsInteger();
530525
// If we can't get emit a sensible last element index, just bail out --
@@ -581,13 +576,9 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C,
581576

582577
auto [StInBound, StOutBound] = state->assumeInBoundDual(*Idx, Size);
583578
if (StOutBound && !StInBound) {
584-
// These checks are either enabled by the CString out-of-bounds checker
585-
// explicitly or implicitly by the Malloc checker.
586-
// In the latter case we only do modeling but do not emit warning.
587-
if (!Filter.CheckCStringOutOfBounds)
579+
if (!OutOfBounds.isEnabled())
588580
return nullptr;
589581

590-
// Emit a bug report.
591582
ErrorMessage Message =
592583
createOutOfBoundErrorMsg(CurrentFunctionDescription, Access);
593584
emitOutOfBoundsBug(C, StOutBound, Buffer.Expression, Message);
@@ -620,7 +611,7 @@ CStringChecker::CheckBufferAccess(CheckerContext &C, ProgramStateRef State,
620611
return nullptr;
621612

622613
// If out-of-bounds checking is turned off, skip the rest.
623-
if (!Filter.CheckCStringOutOfBounds)
614+
if (!OutOfBounds.isEnabled())
624615
return State;
625616

626617
SVal BufStart =
@@ -670,7 +661,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C,
670661
SizeArgExpr Size, AnyArgExpr First,
671662
AnyArgExpr Second,
672663
CharKind CK) const {
673-
if (!Filter.CheckCStringBufferOverlap)
664+
if (!BufferOverlap.isEnabled())
674665
return state;
675666

676667
// Do a simple check for overlap: if the two arguments are from the same
@@ -789,13 +780,9 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state,
789780
if (!N)
790781
return;
791782

792-
if (!BT_Overlap)
793-
BT_Overlap.reset(new BugType(Filter.CheckNameCStringBufferOverlap,
794-
categories::UnixAPI, "Improper arguments"));
795-
796783
// Generate a report for this bug.
797784
auto report = std::make_unique<PathSensitiveBugReport>(
798-
*BT_Overlap, "Arguments must not be overlapping buffers", N);
785+
BufferOverlap, "Arguments must not be overlapping buffers", N);
799786
report->addRange(First->getSourceRange());
800787
report->addRange(Second->getSourceRange());
801788

@@ -805,15 +792,8 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state,
805792
void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State,
806793
const Stmt *S, StringRef WarningMsg) const {
807794
if (ExplodedNode *N = C.generateErrorNode(State)) {
808-
if (!BT_Null) {
809-
// FIXME: This call uses the string constant 'categories::UnixAPI' as the
810-
// description of the bug; it should be replaced by a real description.
811-
BT_Null.reset(
812-
new BugType(Filter.CheckNameCStringNullArg, categories::UnixAPI));
813-
}
814-
815795
auto Report =
816-
std::make_unique<PathSensitiveBugReport>(*BT_Null, WarningMsg, N);
796+
std::make_unique<PathSensitiveBugReport>(NullArg, WarningMsg, N);
817797
Report->addRange(S->getSourceRange());
818798
if (const auto *Ex = dyn_cast<Expr>(S))
819799
bugreporter::trackExpressionValue(N, Ex, *Report);
@@ -826,12 +806,8 @@ void CStringChecker::emitUninitializedReadBug(CheckerContext &C,
826806
const Expr *E, const MemRegion *R,
827807
StringRef Msg) const {
828808
if (ExplodedNode *N = C.generateErrorNode(State)) {
829-
if (!BT_UninitRead)
830-
BT_UninitRead.reset(new BugType(Filter.CheckNameCStringUninitializedRead,
831-
"Accessing unitialized/garbage values"));
832-
833809
auto Report =
834-
std::make_unique<PathSensitiveBugReport>(*BT_UninitRead, Msg, N);
810+
std::make_unique<PathSensitiveBugReport>(UninitializedRead, Msg, N);
835811
Report->addNote("Other elements might also be undefined",
836812
Report->getLocation());
837813
Report->addRange(E->getSourceRange());
@@ -845,17 +821,11 @@ void CStringChecker::emitOutOfBoundsBug(CheckerContext &C,
845821
ProgramStateRef State, const Stmt *S,
846822
StringRef WarningMsg) const {
847823
if (ExplodedNode *N = C.generateErrorNode(State)) {
848-
if (!BT_Bounds)
849-
BT_Bounds.reset(new BugType(Filter.CheckCStringOutOfBounds
850-
? Filter.CheckNameCStringOutOfBounds
851-
: Filter.CheckNameCStringNullArg,
852-
"Out-of-bound array access"));
853-
854824
// FIXME: It would be nice to eventually make this diagnostic more clear,
855825
// e.g., by referencing the original declaration or by saying *why* this
856826
// reference is outside the range.
857827
auto Report =
858-
std::make_unique<PathSensitiveBugReport>(*BT_Bounds, WarningMsg, N);
828+
std::make_unique<PathSensitiveBugReport>(OutOfBounds, WarningMsg, N);
859829
Report->addRange(S->getSourceRange());
860830
C.emitReport(std::move(Report));
861831
}
@@ -865,15 +835,8 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State,
865835
const Stmt *S,
866836
StringRef WarningMsg) const {
867837
if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
868-
if (!BT_NotCString) {
869-
// FIXME: This call uses the string constant 'categories::UnixAPI' as the
870-
// description of the bug; it should be replaced by a real description.
871-
BT_NotCString.reset(
872-
new BugType(Filter.CheckNameCStringNotNullTerm, categories::UnixAPI));
873-
}
874-
875838
auto Report =
876-
std::make_unique<PathSensitiveBugReport>(*BT_NotCString, WarningMsg, N);
839+
std::make_unique<PathSensitiveBugReport>(NotNullTerm, WarningMsg, N);
877840

878841
Report->addRange(S->getSourceRange());
879842
C.emitReport(std::move(Report));
@@ -883,22 +846,14 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State,
883846
void CStringChecker::emitAdditionOverflowBug(CheckerContext &C,
884847
ProgramStateRef State) const {
885848
if (ExplodedNode *N = C.generateErrorNode(State)) {
886-
if (!BT_AdditionOverflow) {
887-
// FIXME: This call uses the word "API" as the description of the bug;
888-
// it should be replaced by a better error message (if this unlikely
889-
// situation continues to exist as a separate bug type).
890-
BT_AdditionOverflow.reset(
891-
new BugType(Filter.CheckNameCStringOutOfBounds, "API"));
892-
}
893-
894849
// This isn't a great error message, but this should never occur in real
895850
// code anyway -- you'd have to create a buffer longer than a size_t can
896851
// represent, which is sort of a contradiction.
897852
const char *WarningMsg =
898853
"This expression will create a string whose length is too big to "
899854
"be represented as a size_t";
900855

901-
auto Report = std::make_unique<PathSensitiveBugReport>(*BT_AdditionOverflow,
856+
auto Report = std::make_unique<PathSensitiveBugReport>(AdditionOverflow,
902857
WarningMsg, N);
903858
C.emitReport(std::move(Report));
904859
}
@@ -909,7 +864,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C,
909864
NonLoc left,
910865
NonLoc right) const {
911866
// If out-of-bounds checking is turned off, skip the rest.
912-
if (!Filter.CheckCStringOutOfBounds)
867+
if (!OutOfBounds.isEnabled())
913868
return state;
914869

915870
// If a previous check has failed, propagate the failure.
@@ -1048,7 +1003,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
10481003
// C string. In the context of locations, the only time we can issue such
10491004
// a warning is for labels.
10501005
if (std::optional<loc::GotoLabel> Label = Buf.getAs<loc::GotoLabel>()) {
1051-
if (Filter.CheckCStringNotNullTerm) {
1006+
if (NotNullTerm.isEnabled()) {
10521007
SmallString<120> buf;
10531008
llvm::raw_svector_ostream os(buf);
10541009
assert(CurrentFunctionDescription);
@@ -1110,7 +1065,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
11101065
// Other regions (mostly non-data) can't have a reliable C string length.
11111066
// In this case, an error is emitted and UndefinedVal is returned.
11121067
// The caller should always be prepared to handle this case.
1113-
if (Filter.CheckCStringNotNullTerm) {
1068+
if (NotNullTerm.isEnabled()) {
11141069
SmallString<120> buf;
11151070
llvm::raw_svector_ostream os(buf);
11161071

@@ -2873,24 +2828,27 @@ void CStringChecker::checkDeadSymbols(SymbolReaper &SR,
28732828
}
28742829

28752830
void ento::registerCStringModeling(CheckerManager &Mgr) {
2876-
Mgr.registerChecker<CStringChecker>();
2831+
// Other checker relies on the modeling implemented in this checker family,
2832+
// so this "modeling checker" can register the 'CStringChecker' backend for
2833+
// its callbacks without enabling any of its frontends.
2834+
Mgr.getChecker<CStringChecker>();
28772835
}
28782836

2879-
bool ento::shouldRegisterCStringModeling(const CheckerManager &mgr) {
2837+
bool ento::shouldRegisterCStringModeling(const CheckerManager &) {
28802838
return true;
28812839
}
28822840

2883-
#define REGISTER_CHECKER(name) \
2884-
void ento::register##name(CheckerManager &mgr) { \
2885-
CStringChecker *checker = mgr.getChecker<CStringChecker>(); \
2886-
checker->Filter.Check##name = true; \
2887-
checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \
2841+
#define REGISTER_CHECKER(NAME) \
2842+
void ento::registerCString##NAME(CheckerManager &Mgr) { \
2843+
Mgr.getChecker<CStringChecker>()->NAME.enable(Mgr); \
28882844
} \
28892845
\
2890-
bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; }
2846+
bool ento::shouldRegisterCString##NAME(const CheckerManager &) { \
2847+
return true; \
2848+
}
28912849

2892-
REGISTER_CHECKER(CStringNullArg)
2893-
REGISTER_CHECKER(CStringOutOfBounds)
2894-
REGISTER_CHECKER(CStringBufferOverlap)
2895-
REGISTER_CHECKER(CStringNotNullTerm)
2896-
REGISTER_CHECKER(CStringUninitializedRead)
2850+
REGISTER_CHECKER(NullArg)
2851+
REGISTER_CHECKER(OutOfBounds)
2852+
REGISTER_CHECKER(BufferOverlap)
2853+
REGISTER_CHECKER(NotNullTerm)
2854+
REGISTER_CHECKER(UninitializedRead)

0 commit comments

Comments
 (0)