[analyzer] Conversion to CheckerFamily: DereferenceChecker (#150442)

This commit converts the class DereferenceChecker to the checker family
framework and simplifies some parts of the implementation.

This commit is almost NFC, the only technically "functional" change is
that it removes the hidden modeling checker `DereferenceModeling` which
was only relevant as an implementation detail of the old checker
registration procedure.
This commit is contained in:
Donát Nagy 2025-07-28 12:20:01 +02:00 committed by GitHub
parent 98ec927fcb
commit bd2b7eb239
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 125 deletions

View File

@ -206,21 +206,15 @@ def CallAndMessageChecker : Checker<"CallAndMessage">,
Documentation<HasDocumentation>, Documentation<HasDocumentation>,
Dependencies<[CallAndMessageModeling]>; Dependencies<[CallAndMessageModeling]>;
def DereferenceModeling : Checker<"DereferenceModeling">,
HelpText<"General support for dereference related checkers">,
Documentation<NotDocumented>,
Hidden;
def FixedAddressDereferenceChecker def FixedAddressDereferenceChecker
: Checker<"FixedAddressDereference">, : Checker<"FixedAddressDereference">,
HelpText<"Check for dereferences of fixed addresses">, HelpText<"Check for dereferences of fixed addresses">,
Documentation<HasDocumentation>, Documentation<HasDocumentation>;
Dependencies<[DereferenceModeling]>;
def NullDereferenceChecker : Checker<"NullDereference">, def NullDereferenceChecker
HelpText<"Check for dereferences of null pointers">, : Checker<"NullDereference">,
Documentation<HasDocumentation>, HelpText<"Check for dereferences of null pointers">,
Dependencies<[DereferenceModeling]>; Documentation<HasDocumentation>;
def NonNullParamChecker : Checker<"NonNullParamChecker">, def NonNullParamChecker : Checker<"NonNullParamChecker">,
HelpText<"Check for null pointers passed as arguments to a function whose " HelpText<"Check for null pointers passed as arguments to a function whose "

View File

@ -25,18 +25,22 @@ using namespace clang;
using namespace ento; using namespace ento;
namespace { namespace {
class DereferenceChecker
: public Checker< check::Location,
check::Bind,
EventDispatcher<ImplicitNullDerefEvent> > {
enum DerefKind {
NullPointer,
UndefinedPointerValue,
AddressOfLabel,
FixedAddress,
};
void reportBug(DerefKind K, ProgramStateRef State, const Stmt *S, class DerefBugType : public BugType {
StringRef ArrayMsg, FieldMsg;
public:
DerefBugType(CheckerFrontend *FE, StringRef Desc, const char *AMsg,
const char *FMsg = nullptr)
: BugType(FE, Desc), ArrayMsg(AMsg), FieldMsg(FMsg ? FMsg : AMsg) {}
StringRef getArrayMsg() const { return ArrayMsg; }
StringRef getFieldMsg() const { return FieldMsg; }
};
class DereferenceChecker
: public CheckerFamily<check::Location, check::Bind,
EventDispatcher<ImplicitNullDerefEvent>> {
void reportBug(const DerefBugType &BT, ProgramStateRef State, const Stmt *S,
CheckerContext &C) const; CheckerContext &C) const;
bool suppressReport(CheckerContext &C, const Expr *E) const; bool suppressReport(CheckerContext &C, const Expr *E) const;
@ -52,13 +56,23 @@ public:
const LocationContext *LCtx, const LocationContext *LCtx,
bool loadedFrom = false); bool loadedFrom = false);
bool CheckNullDereference = false; CheckerFrontend NullDerefChecker, FixedDerefChecker;
bool CheckFixedDereference = false; const DerefBugType NullBug{&NullDerefChecker, "Dereference of null pointer",
"a null pointer dereference",
"a dereference of a null pointer"};
const DerefBugType UndefBug{&NullDerefChecker,
"Dereference of undefined pointer value",
"an undefined pointer dereference",
"a dereference of an undefined pointer value"};
const DerefBugType LabelBug{&NullDerefChecker,
"Dereference of the address of a label",
"an undefined pointer dereference",
"a dereference of an address of a label"};
const DerefBugType FixedAddressBug{&FixedDerefChecker,
"Dereference of a fixed address",
"a dereference of a fixed address"};
std::unique_ptr<BugType> BT_Null; StringRef getDebugTag() const override { return "DereferenceChecker"; }
std::unique_ptr<BugType> BT_Undef;
std::unique_ptr<BugType> BT_Label;
std::unique_ptr<BugType> BT_FixedAddress;
}; };
} // end anonymous namespace } // end anonymous namespace
@ -158,115 +172,87 @@ static bool isDeclRefExprToReference(const Expr *E) {
return false; return false;
} }
void DereferenceChecker::reportBug(DerefKind K, ProgramStateRef State, void DereferenceChecker::reportBug(const DerefBugType &BT,
const Stmt *S, CheckerContext &C) const { ProgramStateRef State, const Stmt *S,
const BugType *BT = nullptr; CheckerContext &C) const {
llvm::StringRef DerefStr1; if (&BT == &FixedAddressBug) {
llvm::StringRef DerefStr2; if (!FixedDerefChecker.isEnabled())
switch (K) { // Deliberately don't add a sink node if check is disabled.
case DerefKind::NullPointer: // This situation may be valid in special cases.
if (!CheckNullDereference) { return;
} else {
if (!NullDerefChecker.isEnabled()) {
C.addSink(); C.addSink();
return; return;
} }
BT = BT_Null.get(); }
DerefStr1 = " results in a null pointer dereference";
DerefStr2 = " results in a dereference of a null pointer";
break;
case DerefKind::UndefinedPointerValue:
if (!CheckNullDereference) {
C.addSink();
return;
}
BT = BT_Undef.get();
DerefStr1 = " results in an undefined pointer dereference";
DerefStr2 = " results in a dereference of an undefined pointer value";
break;
case DerefKind::AddressOfLabel:
if (!CheckNullDereference) {
C.addSink();
return;
}
BT = BT_Label.get();
DerefStr1 = " results in an undefined pointer dereference";
DerefStr2 = " results in a dereference of an address of a label";
break;
case DerefKind::FixedAddress:
// Deliberately don't add a sink node if check is disabled.
// This situation may be valid in special cases.
if (!CheckFixedDereference)
return;
BT = BT_FixedAddress.get();
DerefStr1 = " results in a dereference of a fixed address";
DerefStr2 = " results in a dereference of a fixed address";
break;
};
// Generate an error node. // Generate an error node.
ExplodedNode *N = C.generateErrorNode(State); ExplodedNode *N = C.generateErrorNode(State);
if (!N) if (!N)
return; return;
SmallString<100> buf; SmallString<100> Buf;
llvm::raw_svector_ostream os(buf); llvm::raw_svector_ostream Out(Buf);
SmallVector<SourceRange, 2> Ranges; SmallVector<SourceRange, 2> Ranges;
switch (S->getStmtClass()) { switch (S->getStmtClass()) {
case Stmt::ArraySubscriptExprClass: { case Stmt::ArraySubscriptExprClass: {
os << "Array access"; Out << "Array access";
const ArraySubscriptExpr *AE = cast<ArraySubscriptExpr>(S); const ArraySubscriptExpr *AE = cast<ArraySubscriptExpr>(S);
AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), AddDerefSource(Out, Ranges, AE->getBase()->IgnoreParenCasts(), State.get(),
State.get(), N->getLocationContext()); N->getLocationContext());
os << DerefStr1; Out << " results in " << BT.getArrayMsg();
break; break;
} }
case Stmt::ArraySectionExprClass: { case Stmt::ArraySectionExprClass: {
os << "Array access"; Out << "Array access";
const ArraySectionExpr *AE = cast<ArraySectionExpr>(S); const ArraySectionExpr *AE = cast<ArraySectionExpr>(S);
AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), AddDerefSource(Out, Ranges, AE->getBase()->IgnoreParenCasts(), State.get(),
State.get(), N->getLocationContext()); N->getLocationContext());
os << DerefStr1; Out << " results in " << BT.getArrayMsg();
break; break;
} }
case Stmt::UnaryOperatorClass: { case Stmt::UnaryOperatorClass: {
os << BT->getDescription(); Out << BT.getDescription();
const UnaryOperator *U = cast<UnaryOperator>(S); const UnaryOperator *U = cast<UnaryOperator>(S);
AddDerefSource(os, Ranges, U->getSubExpr()->IgnoreParens(), AddDerefSource(Out, Ranges, U->getSubExpr()->IgnoreParens(), State.get(),
State.get(), N->getLocationContext(), true); N->getLocationContext(), true);
break; break;
} }
case Stmt::MemberExprClass: { case Stmt::MemberExprClass: {
const MemberExpr *M = cast<MemberExpr>(S); const MemberExpr *M = cast<MemberExpr>(S);
if (M->isArrow() || isDeclRefExprToReference(M->getBase())) { if (M->isArrow() || isDeclRefExprToReference(M->getBase())) {
os << "Access to field '" << M->getMemberNameInfo() << "'" << DerefStr2; Out << "Access to field '" << M->getMemberNameInfo() << "' results in "
AddDerefSource(os, Ranges, M->getBase()->IgnoreParenCasts(), << BT.getFieldMsg();
State.get(), N->getLocationContext(), true); AddDerefSource(Out, Ranges, M->getBase()->IgnoreParenCasts(), State.get(),
N->getLocationContext(), true);
} }
break; break;
} }
case Stmt::ObjCIvarRefExprClass: { case Stmt::ObjCIvarRefExprClass: {
const ObjCIvarRefExpr *IV = cast<ObjCIvarRefExpr>(S); const ObjCIvarRefExpr *IV = cast<ObjCIvarRefExpr>(S);
os << "Access to instance variable '" << *IV->getDecl() << "'" << DerefStr2; Out << "Access to instance variable '" << *IV->getDecl() << "' results in "
AddDerefSource(os, Ranges, IV->getBase()->IgnoreParenCasts(), << BT.getFieldMsg();
State.get(), N->getLocationContext(), true); AddDerefSource(Out, Ranges, IV->getBase()->IgnoreParenCasts(), State.get(),
N->getLocationContext(), true);
break; break;
} }
default: default:
break; break;
} }
auto report = std::make_unique<PathSensitiveBugReport>( auto BR = std::make_unique<PathSensitiveBugReport>(
*BT, buf.empty() ? BT->getDescription() : buf.str(), N); BT, Buf.empty() ? BT.getDescription() : Buf.str(), N);
bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *BR);
for (SmallVectorImpl<SourceRange>::iterator for (SmallVectorImpl<SourceRange>::iterator
I = Ranges.begin(), E = Ranges.end(); I!=E; ++I) I = Ranges.begin(), E = Ranges.end(); I!=E; ++I)
report->addRange(*I); BR->addRange(*I);
C.emitReport(std::move(report)); C.emitReport(std::move(BR));
} }
void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
@ -275,7 +261,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
if (l.isUndef()) { if (l.isUndef()) {
const Expr *DerefExpr = getDereferenceExpr(S); const Expr *DerefExpr = getDereferenceExpr(S);
if (!suppressReport(C, DerefExpr)) if (!suppressReport(C, DerefExpr))
reportBug(DerefKind::UndefinedPointerValue, C.getState(), DerefExpr, C); reportBug(UndefBug, C.getState(), DerefExpr, C);
return; return;
} }
@ -296,7 +282,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
// we call an "explicit" null dereference. // we call an "explicit" null dereference.
const Expr *expr = getDereferenceExpr(S); const Expr *expr = getDereferenceExpr(S);
if (!suppressReport(C, expr)) { if (!suppressReport(C, expr)) {
reportBug(DerefKind::NullPointer, nullState, expr, C); reportBug(NullBug, nullState, expr, C);
return; return;
} }
} }
@ -314,7 +300,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
if (location.isConstant()) { if (location.isConstant()) {
const Expr *DerefExpr = getDereferenceExpr(S, isLoad); const Expr *DerefExpr = getDereferenceExpr(S, isLoad);
if (!suppressReport(C, DerefExpr)) if (!suppressReport(C, DerefExpr))
reportBug(DerefKind::FixedAddress, notNullState, DerefExpr, C); reportBug(FixedAddressBug, notNullState, DerefExpr, C);
return; return;
} }
@ -330,7 +316,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
// One should never write to label addresses. // One should never write to label addresses.
if (auto Label = L.getAs<loc::GotoLabel>()) { if (auto Label = L.getAs<loc::GotoLabel>()) {
reportBug(DerefKind::AddressOfLabel, C.getState(), S, C); reportBug(LabelBug, C.getState(), S, C);
return; return;
} }
@ -351,7 +337,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
if (!StNonNull) { if (!StNonNull) {
const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true); const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true);
if (!suppressReport(C, expr)) { if (!suppressReport(C, expr)) {
reportBug(DerefKind::NullPointer, StNull, expr, C); reportBug(NullBug, StNull, expr, C);
return; return;
} }
} }
@ -369,7 +355,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
if (V.isConstant()) { if (V.isConstant()) {
const Expr *DerefExpr = getDereferenceExpr(S, true); const Expr *DerefExpr = getDereferenceExpr(S, true);
if (!suppressReport(C, DerefExpr)) if (!suppressReport(C, DerefExpr))
reportBug(DerefKind::FixedAddress, State, DerefExpr, C); reportBug(FixedAddressBug, State, DerefExpr, C);
return; return;
} }
@ -392,26 +378,8 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
C.addTransition(State, this); C.addTransition(State, this);
} }
void ento::registerDereferenceModeling(CheckerManager &Mgr) {
Mgr.registerChecker<DereferenceChecker>();
}
bool ento::shouldRegisterDereferenceModeling(const CheckerManager &) {
return true;
}
void ento::registerNullDereferenceChecker(CheckerManager &Mgr) { void ento::registerNullDereferenceChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.getChecker<DereferenceChecker>(); Mgr.getChecker<DereferenceChecker>()->NullDerefChecker.enable(Mgr);
Chk->CheckNullDereference = true;
Chk->BT_Null.reset(new BugType(Mgr.getCurrentCheckerName(),
"Dereference of null pointer",
categories::LogicError));
Chk->BT_Undef.reset(new BugType(Mgr.getCurrentCheckerName(),
"Dereference of undefined pointer value",
categories::LogicError));
Chk->BT_Label.reset(new BugType(Mgr.getCurrentCheckerName(),
"Dereference of the address of a label",
categories::LogicError));
} }
bool ento::shouldRegisterNullDereferenceChecker(const CheckerManager &) { bool ento::shouldRegisterNullDereferenceChecker(const CheckerManager &) {
@ -419,11 +387,7 @@ bool ento::shouldRegisterNullDereferenceChecker(const CheckerManager &) {
} }
void ento::registerFixedAddressDereferenceChecker(CheckerManager &Mgr) { void ento::registerFixedAddressDereferenceChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.getChecker<DereferenceChecker>(); Mgr.getChecker<DereferenceChecker>()->FixedDerefChecker.enable(Mgr);
Chk->CheckFixedDereference = true;
Chk->BT_FixedAddress.reset(new BugType(Mgr.getCurrentCheckerName(),
"Dereference of a fixed address",
categories::LogicError));
} }
bool ento::shouldRegisterFixedAddressDereferenceChecker( bool ento::shouldRegisterFixedAddressDereferenceChecker(

View File

@ -14,7 +14,6 @@
// CHECK-NEXT: core.BitwiseShift // CHECK-NEXT: core.BitwiseShift
// CHECK-NEXT: core.CallAndMessageModeling // CHECK-NEXT: core.CallAndMessageModeling
// CHECK-NEXT: core.CallAndMessage // CHECK-NEXT: core.CallAndMessage
// CHECK-NEXT: core.DereferenceModeling
// CHECK-NEXT: core.DivideZero // CHECK-NEXT: core.DivideZero
// CHECK-NEXT: core.DynamicTypePropagation // CHECK-NEXT: core.DynamicTypePropagation
// CHECK-NEXT: core.FixedAddressDereference // CHECK-NEXT: core.FixedAddressDereference

View File

@ -22,7 +22,6 @@
// CHECK-NEXT: core.BitwiseShift // CHECK-NEXT: core.BitwiseShift
// CHECK-NEXT: core.CallAndMessageModeling // CHECK-NEXT: core.CallAndMessageModeling
// CHECK-NEXT: core.CallAndMessage // CHECK-NEXT: core.CallAndMessage
// CHECK-NEXT: core.DereferenceModeling
// CHECK-NEXT: core.DivideZero // CHECK-NEXT: core.DivideZero
// CHECK-NEXT: core.DynamicTypePropagation // CHECK-NEXT: core.DynamicTypePropagation
// CHECK-NEXT: core.FixedAddressDereference // CHECK-NEXT: core.FixedAddressDereference