Skip to content

[analyzer] Explicitly register NoStoreFuncVisitor from alpha.unix.cst… #108373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,91 @@ class NoStateChangeFuncVisitor : public BugReporterVisitor {
PathSensitiveBugReport &R) final;
};

/// Put a diagnostic on return statement of all inlined functions
/// for which the region of interest \p RegionOfInterest was passed into,
/// but not written inside, and it has caused an undefined read or a null
/// pointer dereference outside.
class NoStoreFuncVisitor final : public NoStateChangeFuncVisitor {
const SubRegion *RegionOfInterest;
MemRegionManager &MmrMgr;
const SourceManager &SM;
const PrintingPolicy &PP;

/// Recursion limit for dereferencing fields when looking for the
/// region of interest.
/// The limit of two indicates that we will dereference fields only once.
static const unsigned DEREFERENCE_LIMIT = 2;

using RegionVector = SmallVector<const MemRegion *, 5>;

public:
NoStoreFuncVisitor(
const SubRegion *R,
bugreporter::TrackingKind TKind = bugreporter::TrackingKind::Thorough)
: NoStateChangeFuncVisitor(TKind), RegionOfInterest(R),
MmrMgr(R->getMemRegionManager()),
SM(MmrMgr.getContext().getSourceManager()),
PP(MmrMgr.getContext().getPrintingPolicy()) {}

void Profile(llvm::FoldingSetNodeID &ID) const override {
static int Tag = 0;
ID.AddPointer(&Tag);
ID.AddPointer(RegionOfInterest);
}

private:
/// \return Whether \c RegionOfInterest was modified at \p CurrN compared to
/// the value it holds in \p CallExitBeginN.
bool wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
const ExplodedNode *CallExitBeginN) override;

/// Attempts to find the region of interest in a given record decl,
/// by either following the base classes or fields.
/// Dereferences fields up to a given recursion limit.
/// Note that \p Vec is passed by value, leading to quadratic copying cost,
/// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT.
/// \return A chain fields leading to the region of interest or std::nullopt.
const std::optional<RegionVector>
findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State,
const MemRegion *R, const RegionVector &Vec = {},
int depth = 0);

// Region of interest corresponds to an IVar, exiting a method
// which could have written into that IVar, but did not.
PathDiagnosticPieceRef maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R,
const ObjCMethodCall &Call,
const ExplodedNode *N) final;

PathDiagnosticPieceRef maybeEmitNoteForCXXThis(PathSensitiveBugReport &R,
const CXXConstructorCall &Call,
const ExplodedNode *N) final;

PathDiagnosticPieceRef
maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call,
const ExplodedNode *N) final;

/// Consume the information on the no-store stack frame in order to
/// either emit a note or suppress the report entirely.
/// \return Diagnostics piece for region not modified in the current function,
/// if it decides to emit one.
PathDiagnosticPieceRef
maybeEmitNote(PathSensitiveBugReport &R, const CallEvent &Call,
const ExplodedNode *N, const RegionVector &FieldChain,
const MemRegion *MatchedRegion, StringRef FirstElement,
bool FirstIsReferenceType, unsigned IndirectionLevel);

bool prettyPrintRegionName(const RegionVector &FieldChain,
const MemRegion *MatchedRegion,
StringRef FirstElement, bool FirstIsReferenceType,
unsigned IndirectionLevel,
llvm::raw_svector_ostream &os);

StringRef prettyPrintFirstElement(StringRef FirstElement,
bool MoreItemsExpected,
int IndirectionLevel,
llvm::raw_svector_ostream &os);
};

} // namespace ento
} // namespace clang

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class CheckerContext {
/// the state of the program before the checker ran. Note, checkers should
/// not retain the node in their state since the nodes might get invalidated.
ExplodedNode *getPredecessor() { return Pred; }
const ProgramPoint getLocation() const { return Location; }
const ProgramStateRef &getState() const { return Pred->getState(); }

/// Check if the checker changed the state of the execution; ex: added
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ class SVal {

SValKind getKind() const { return Kind; }

StringRef getKindStr() const;

// This method is required for using SVal in a FoldingSetNode. It
// extracts a unique signature for this SVal object.
void Profile(llvm::FoldingSetNodeID &ID) const {
Expand Down
13 changes: 9 additions & 4 deletions clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CharInfo.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
Expand Down Expand Up @@ -337,7 +338,8 @@ class CStringChecker : public Checker< eval::Call,
const Stmt *S, StringRef WarningMsg) const;
void emitAdditionOverflowBug(CheckerContext &C, ProgramStateRef State) const;
void emitUninitializedReadBug(CheckerContext &C, ProgramStateRef State,
const Expr *E, StringRef Msg) const;
const Expr *E, const MemRegion *R,
StringRef Msg) const;
ProgramStateRef checkAdditionOverflow(CheckerContext &C,
ProgramStateRef state,
NonLoc left,
Expand Down Expand Up @@ -474,7 +476,8 @@ ProgramStateRef CStringChecker::checkInit(CheckerContext &C,
OS << "The first element of the ";
printIdxWithOrdinalSuffix(OS, Buffer.ArgumentIndex + 1);
OS << " argument is undefined";
emitUninitializedReadBug(C, State, Buffer.Expression, OS.str());
emitUninitializedReadBug(C, State, Buffer.Expression,
FirstElementVal->getAsRegion(), OS.str());
return nullptr;
}

Expand Down Expand Up @@ -538,7 +541,8 @@ ProgramStateRef CStringChecker::checkInit(CheckerContext &C,
OS << ") in the ";
printIdxWithOrdinalSuffix(OS, Buffer.ArgumentIndex + 1);
OS << " argument is undefined";
emitUninitializedReadBug(C, State, Buffer.Expression, OS.str());
emitUninitializedReadBug(C, State, Buffer.Expression,
LastElementVal.getAsRegion(), OS.str());
return nullptr;
}
return State;
Expand Down Expand Up @@ -818,7 +822,7 @@ void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State,

void CStringChecker::emitUninitializedReadBug(CheckerContext &C,
ProgramStateRef State,
const Expr *E,
const Expr *E, const MemRegion *R,
StringRef Msg) const {
if (ExplodedNode *N = C.generateErrorNode(State)) {
if (!BT_UninitRead)
Expand All @@ -831,6 +835,7 @@ void CStringChecker::emitUninitializedReadBug(CheckerContext &C,
Report->getLocation());
Report->addRange(E->getSourceRange());
bugreporter::trackExpressionValue(N, E, *Report);
Report->addVisitor<NoStoreFuncVisitor>(R->castAs<SubRegion>());
C.emitReport(std::move(Report));
}
}
Expand Down
89 changes: 0 additions & 89 deletions clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,95 +522,6 @@ PathDiagnosticPieceRef NoStateChangeFuncVisitor::VisitNode(
return maybeEmitNoteForParameters(R, *Call, N);
}

//===----------------------------------------------------------------------===//
// Implementation of NoStoreFuncVisitor.
//===----------------------------------------------------------------------===//

namespace {
/// Put a diagnostic on return statement of all inlined functions
/// for which the region of interest \p RegionOfInterest was passed into,
/// but not written inside, and it has caused an undefined read or a null
/// pointer dereference outside.
class NoStoreFuncVisitor final : public NoStateChangeFuncVisitor {
const SubRegion *RegionOfInterest;
MemRegionManager &MmrMgr;
const SourceManager &SM;
const PrintingPolicy &PP;

/// Recursion limit for dereferencing fields when looking for the
/// region of interest.
/// The limit of two indicates that we will dereference fields only once.
static const unsigned DEREFERENCE_LIMIT = 2;

using RegionVector = SmallVector<const MemRegion *, 5>;

public:
NoStoreFuncVisitor(const SubRegion *R, bugreporter::TrackingKind TKind)
: NoStateChangeFuncVisitor(TKind), RegionOfInterest(R),
MmrMgr(R->getMemRegionManager()),
SM(MmrMgr.getContext().getSourceManager()),
PP(MmrMgr.getContext().getPrintingPolicy()) {}

void Profile(llvm::FoldingSetNodeID &ID) const override {
static int Tag = 0;
ID.AddPointer(&Tag);
ID.AddPointer(RegionOfInterest);
}

private:
/// \return Whether \c RegionOfInterest was modified at \p CurrN compared to
/// the value it holds in \p CallExitBeginN.
bool wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
const ExplodedNode *CallExitBeginN) override;

/// Attempts to find the region of interest in a given record decl,
/// by either following the base classes or fields.
/// Dereferences fields up to a given recursion limit.
/// Note that \p Vec is passed by value, leading to quadratic copying cost,
/// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT.
/// \return A chain fields leading to the region of interest or std::nullopt.
const std::optional<RegionVector>
findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State,
const MemRegion *R, const RegionVector &Vec = {},
int depth = 0);

// Region of interest corresponds to an IVar, exiting a method
// which could have written into that IVar, but did not.
PathDiagnosticPieceRef maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R,
const ObjCMethodCall &Call,
const ExplodedNode *N) final;

PathDiagnosticPieceRef maybeEmitNoteForCXXThis(PathSensitiveBugReport &R,
const CXXConstructorCall &Call,
const ExplodedNode *N) final;

PathDiagnosticPieceRef
maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call,
const ExplodedNode *N) final;

/// Consume the information on the no-store stack frame in order to
/// either emit a note or suppress the report enirely.
/// \return Diagnostics piece for region not modified in the current function,
/// if it decides to emit one.
PathDiagnosticPieceRef
maybeEmitNote(PathSensitiveBugReport &R, const CallEvent &Call,
const ExplodedNode *N, const RegionVector &FieldChain,
const MemRegion *MatchedRegion, StringRef FirstElement,
bool FirstIsReferenceType, unsigned IndirectionLevel);

bool prettyPrintRegionName(const RegionVector &FieldChain,
const MemRegion *MatchedRegion,
StringRef FirstElement, bool FirstIsReferenceType,
unsigned IndirectionLevel,
llvm::raw_svector_ostream &os);

StringRef prettyPrintFirstElement(StringRef FirstElement,
bool MoreItemsExpected,
int IndirectionLevel,
llvm::raw_svector_ostream &os);
};
} // namespace

/// \return Whether the method declaration \p Parent
/// syntactically has a binary operation writing into the ivar \p Ivar.
static bool potentiallyWritesIntoIvar(const Decl *Parent,
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/StaticAnalyzer/Core/SVals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,23 @@ bool SVal::isZeroConstant() const {
// Pretty-Printing.
//===----------------------------------------------------------------------===//

StringRef SVal::getKindStr() const {
switch (getKind()) {
#define BASIC_SVAL(Id, Parent) \
case Id##Kind: \
return #Id;
#define LOC_SVAL(Id, Parent) \
case Loc##Id##Kind: \
return #Id;
#define NONLOC_SVAL(Id, Parent) \
case NonLoc##Id##Kind: \
return #Id;
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.def"
#undef REGION
}
llvm_unreachable("Unkown kind!");
}

LLVM_DUMP_METHOD void SVal::dump() const { dumpToStream(llvm::errs()); }

void SVal::printJson(raw_ostream &Out, bool AddQuotes) const {
Expand Down
25 changes: 25 additions & 0 deletions clang/test/Analysis/cstring-uninitread-notes.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %clang_analyze_cc1 -verify %s \
// RUN: -analyzer-checker=core,alpha.unix.cstring \
// RUN: -analyzer-output=text

#include "Inputs/system-header-simulator.h"

// Inspired by a report on ffmpeg, libavcodec/tiertexseqv.c, seq_decode_op1().
int coin();

void maybeWrite(const char *src, unsigned size, int *dst) {
if (coin()) // expected-note{{Assuming the condition is false}}
// expected-note@-1{{Taking false branch}}
memcpy(dst, src, size);
} // expected-note{{Returning without writing to '*dst'}}

void returning_without_writing_to_memcpy(const char *src, unsigned size) {
int block[8 * 8]; // expected-note{{'block' initialized here}}
// expected-note@+1{{Calling 'maybeWrite'}}
maybeWrite(src, size, block); // expected-note{{Returning from 'maybeWrite'}}

int buf[8 * 8];
memcpy(buf, &block[0], 8); // expected-warning{{The first element of the 2nd argument is undefined [alpha.unix.cstring.UninitializedRead]}}
// expected-note@-1{{The first element of the 2nd argument is undefined}}
// expected-note@-2{{Other elements might also be undefined}}
}
Loading