Skip to content

Commit 3422f5a

Browse files
committed
[NFC][analyzer] Conversion to CheckerFamily: CStringChecker
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 e38f98f commit 3422f5a

File tree

1 file changed

+52
-90
lines changed

1 file changed

+52
-90
lines changed

clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp

Lines changed: 52 additions & 90 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 --
@@ -584,7 +579,7 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C,
584579
// These checks are either enabled by the CString out-of-bounds checker
585580
// explicitly or implicitly by the Malloc checker.
586581
// In the latter case we only do modeling but do not emit warning.
587-
if (!Filter.CheckCStringOutOfBounds)
582+
if (!OutOfBounds.isEnabled())
588583
return nullptr;
589584

590585
// Emit a bug report.
@@ -620,7 +615,7 @@ CStringChecker::CheckBufferAccess(CheckerContext &C, ProgramStateRef State,
620615
return nullptr;
621616

622617
// If out-of-bounds checking is turned off, skip the rest.
623-
if (!Filter.CheckCStringOutOfBounds)
618+
if (!OutOfBounds.isEnabled())
624619
return State;
625620

626621
SVal BufStart =
@@ -670,7 +665,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C,
670665
SizeArgExpr Size, AnyArgExpr First,
671666
AnyArgExpr Second,
672667
CharKind CK) const {
673-
if (!Filter.CheckCStringBufferOverlap)
668+
if (!BufferOverlap.isEnabled())
674669
return state;
675670

676671
// Do a simple check for overlap: if the two arguments are from the same
@@ -789,13 +784,9 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state,
789784
if (!N)
790785
return;
791786

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

@@ -805,15 +796,8 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state,
805796
void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State,
806797
const Stmt *S, StringRef WarningMsg) const {
807798
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-
815799
auto Report =
816-
std::make_unique<PathSensitiveBugReport>(*BT_Null, WarningMsg, N);
800+
std::make_unique<PathSensitiveBugReport>(NullArg, WarningMsg, N);
817801
Report->addRange(S->getSourceRange());
818802
if (const auto *Ex = dyn_cast<Expr>(S))
819803
bugreporter::trackExpressionValue(N, Ex, *Report);
@@ -826,12 +810,8 @@ void CStringChecker::emitUninitializedReadBug(CheckerContext &C,
826810
const Expr *E, const MemRegion *R,
827811
StringRef Msg) const {
828812
if (ExplodedNode *N = C.generateErrorNode(State)) {
829-
if (!BT_UninitRead)
830-
BT_UninitRead.reset(new BugType(Filter.CheckNameCStringUninitializedRead,
831-
"Accessing unitialized/garbage values"));
832-
833813
auto Report =
834-
std::make_unique<PathSensitiveBugReport>(*BT_UninitRead, Msg, N);
814+
std::make_unique<PathSensitiveBugReport>(UninitializedRead, Msg, N);
835815
Report->addNote("Other elements might also be undefined",
836816
Report->getLocation());
837817
Report->addRange(E->getSourceRange());
@@ -845,17 +825,11 @@ void CStringChecker::emitOutOfBoundsBug(CheckerContext &C,
845825
ProgramStateRef State, const Stmt *S,
846826
StringRef WarningMsg) const {
847827
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-
854828
// FIXME: It would be nice to eventually make this diagnostic more clear,
855829
// e.g., by referencing the original declaration or by saying *why* this
856830
// reference is outside the range.
857831
auto Report =
858-
std::make_unique<PathSensitiveBugReport>(*BT_Bounds, WarningMsg, N);
832+
std::make_unique<PathSensitiveBugReport>(OutOfBounds, WarningMsg, N);
859833
Report->addRange(S->getSourceRange());
860834
C.emitReport(std::move(Report));
861835
}
@@ -865,15 +839,8 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State,
865839
const Stmt *S,
866840
StringRef WarningMsg) const {
867841
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-
875842
auto Report =
876-
std::make_unique<PathSensitiveBugReport>(*BT_NotCString, WarningMsg, N);
843+
std::make_unique<PathSensitiveBugReport>(NotNullTerm, WarningMsg, N);
877844

878845
Report->addRange(S->getSourceRange());
879846
C.emitReport(std::move(Report));
@@ -883,22 +850,14 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State,
883850
void CStringChecker::emitAdditionOverflowBug(CheckerContext &C,
884851
ProgramStateRef State) const {
885852
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-
894853
// This isn't a great error message, but this should never occur in real
895854
// code anyway -- you'd have to create a buffer longer than a size_t can
896855
// represent, which is sort of a contradiction.
897856
const char *WarningMsg =
898857
"This expression will create a string whose length is too big to "
899858
"be represented as a size_t";
900859

901-
auto Report = std::make_unique<PathSensitiveBugReport>(*BT_AdditionOverflow,
860+
auto Report = std::make_unique<PathSensitiveBugReport>(AdditionOverflow,
902861
WarningMsg, N);
903862
C.emitReport(std::move(Report));
904863
}
@@ -909,7 +868,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C,
909868
NonLoc left,
910869
NonLoc right) const {
911870
// If out-of-bounds checking is turned off, skip the rest.
912-
if (!Filter.CheckCStringOutOfBounds)
871+
if (!OutOfBounds.isEnabled())
913872
return state;
914873

915874
// If a previous check has failed, propagate the failure.
@@ -1048,7 +1007,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
10481007
// C string. In the context of locations, the only time we can issue such
10491008
// a warning is for labels.
10501009
if (std::optional<loc::GotoLabel> Label = Buf.getAs<loc::GotoLabel>()) {
1051-
if (Filter.CheckCStringNotNullTerm) {
1010+
if (NotNullTerm.isEnabled()) {
10521011
SmallString<120> buf;
10531012
llvm::raw_svector_ostream os(buf);
10541013
assert(CurrentFunctionDescription);
@@ -1110,7 +1069,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
11101069
// Other regions (mostly non-data) can't have a reliable C string length.
11111070
// In this case, an error is emitted and UndefinedVal is returned.
11121071
// The caller should always be prepared to handle this case.
1113-
if (Filter.CheckCStringNotNullTerm) {
1072+
if (NotNullTerm.isEnabled()) {
11141073
SmallString<120> buf;
11151074
llvm::raw_svector_ostream os(buf);
11161075

@@ -2873,24 +2832,27 @@ void CStringChecker::checkDeadSymbols(SymbolReaper &SR,
28732832
}
28742833

28752834
void ento::registerCStringModeling(CheckerManager &Mgr) {
2876-
Mgr.registerChecker<CStringChecker>();
2835+
// Other checker relies on the modeling implemented in this checker family,
2836+
// so this "modeling checker" can register the 'CStringChecker' backend for
2837+
// its callbacks without enabling any of its frontends.
2838+
Mgr.getChecker<CStringChecker>();
28772839
}
28782840

2879-
bool ento::shouldRegisterCStringModeling(const CheckerManager &mgr) {
2841+
bool ento::shouldRegisterCStringModeling(const CheckerManager &) {
28802842
return true;
28812843
}
28822844

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(); \
2845+
#define REGISTER_CHECKER(NAME) \
2846+
void ento::registerCString##NAME(CheckerManager &Mgr) { \
2847+
Mgr.getChecker<CStringChecker>()->NAME.enable(Mgr); \
28882848
} \
28892849
\
2890-
bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; }
2850+
bool ento::shouldRegisterCString##NAME(const CheckerManager &) { \
2851+
return true; \
2852+
}
28912853

2892-
REGISTER_CHECKER(CStringNullArg)
2893-
REGISTER_CHECKER(CStringOutOfBounds)
2894-
REGISTER_CHECKER(CStringBufferOverlap)
2895-
REGISTER_CHECKER(CStringNotNullTerm)
2896-
REGISTER_CHECKER(CStringUninitializedRead)
2854+
REGISTER_CHECKER(NullArg)
2855+
REGISTER_CHECKER(OutOfBounds)
2856+
REGISTER_CHECKER(BufferOverlap)
2857+
REGISTER_CHECKER(NotNullTerm)
2858+
REGISTER_CHECKER(UninitializedRead)

0 commit comments

Comments
 (0)