Skip to content

Commit cc82321

Browse files
committed
[analyzer] Explicitly register NoStoreFuncVisitor from alpha.unix.cstring.UninitRead
This is a drastic simplification of llvm#106982. The patch was inspired by a pretty poor bug report on FFMpeg: https://codechecker-demo.eastus.cloudapp.azure.com/Default/report-detail?is-unique=on&run=curl_curl-7_66_0_cstring_uninit_baseline&run=ffmpeg_n4.3.1_cstring_uninit_baseline&run=memcached_1.6.8_cstring_uninit_baseline&run=openssl_openssl-3.0.0-alpha7_cstring_uninit_baseline&run=sqlite_version-3.33.0_cstring_uninit_baseline&run=tmux_2.6_cstring_uninit_baseline&run=twin_v0.8.1_cstring_uninit_baseline&run=vim_v8.2.1920_cstring_uninit_baseline&newcheck=curl_curl-7_66_0_cstring_uninit_final&newcheck=ffmpeg_n4.3.1_cstring_uninit_final&newcheck=memcached_1.6.8_cstring_uninit_final&newcheck=openssl_openssl-3.0.0-alpha7_cstring_uninit_final&newcheck=sqlite_version-3.33.0_cstring_uninit_final&newcheck=tmux_2.6_cstring_uninit_final&newcheck=twin_v0.8.1_cstring_uninit_final&newcheck=vim_v8.2.1920_cstring_uninit_final&diff-type=Unresolved&items-per-page=100&checker-name=alpha.unix.cstring.UninitializedRead&report-id=5766433&report-hash=82c63868ba782bfa216ee3f2b5734d6b&report-filepath=%2atiertexseqv.c In this bug report, block is uninitialized, hence the bug report that it should not have been passed to memcpy. The confusing part is in line 93, where block was passed as a non-const pointer to seq_unpack_rle_block, which was obviously meant to initialize block. As developers, we know that clang likely didn't skip this function and found a path of execution on which this initialization failed, but NoStoreFuncVisitor failed to attach the usual "returning without writing to block" message. I fixed this by instead of tracking the entire array, I tracked the actual element which was found to be uninitialized (Remember, we heuristically only check if the first and last-to-access element is initialized, not the entire array). This is how the bug report looks now: https://codechecker-demo.eastus.cloudapp.azure.com/Default/report-detail?is-unique=on&run=curl_curl-7_66_0_cstring_uninit_baseline&run=ffmpeg_n4.3.1_cstring_uninit_baseline&run=memcached_1.6.8_cstring_uninit_baseline&run=openssl_openssl-3.0.0-alpha7_cstring_uninit_baseline&run=sqlite_version-3.33.0_cstring_uninit_baseline&run=tmux_2.6_cstring_uninit_baseline&run=twin_v0.8.1_cstring_uninit_baseline&run=vim_v8.2.1920_cstring_uninit_baseline&newcheck=curl_curl-7_66_0_cstring_uninit_final&newcheck=ffmpeg_n4.3.1_cstring_uninit_final&newcheck=memcached_1.6.8_cstring_uninit_final&newcheck=openssl_openssl-3.0.0-alpha7_cstring_uninit_final&newcheck=sqlite_version-3.33.0_cstring_uninit_final&newcheck=tmux_2.6_cstring_uninit_final&newcheck=twin_v0.8.1_cstring_uninit_final&newcheck=vim_v8.2.1920_cstring_uninit_final&diff-type=Unresolved&items-per-page=100&checker-name=alpha.unix.cstring.UninitializedRead&report-id=5768860&report-hash=82c63868ba782bfa216ee3f2b5734d6b&report-filepath=%2atiertexseqv.c Since NoStoreFuncVisitor was a TU-local class, I moved it back to BugReporterVisitors.h, and registered it manually in CStringChecker.cpp. This was done because we don't have a good trackRegionValue() function, only a trackExpressionValue() function. We have an expression for the array, but not for its first (or last-to-access) element, so I only had a MemRegion on hand.
1 parent 181cc75 commit cc82321

File tree

7 files changed

+139
-93
lines changed

7 files changed

+139
-93
lines changed

clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,91 @@ class NoStateChangeFuncVisitor : public BugReporterVisitor {
718718
PathSensitiveBugReport &R) final;
719719
};
720720

721+
/// Put a diagnostic on return statement of all inlined functions
722+
/// for which the region of interest \p RegionOfInterest was passed into,
723+
/// but not written inside, and it has caused an undefined read or a null
724+
/// pointer dereference outside.
725+
class NoStoreFuncVisitor final : public NoStateChangeFuncVisitor {
726+
const SubRegion *RegionOfInterest;
727+
MemRegionManager &MmrMgr;
728+
const SourceManager &SM;
729+
const PrintingPolicy &PP;
730+
731+
/// Recursion limit for dereferencing fields when looking for the
732+
/// region of interest.
733+
/// The limit of two indicates that we will dereference fields only once.
734+
static const unsigned DEREFERENCE_LIMIT = 2;
735+
736+
using RegionVector = SmallVector<const MemRegion *, 5>;
737+
738+
public:
739+
NoStoreFuncVisitor(
740+
const SubRegion *R,
741+
bugreporter::TrackingKind TKind = bugreporter::TrackingKind::Thorough)
742+
: NoStateChangeFuncVisitor(TKind), RegionOfInterest(R),
743+
MmrMgr(R->getMemRegionManager()),
744+
SM(MmrMgr.getContext().getSourceManager()),
745+
PP(MmrMgr.getContext().getPrintingPolicy()) {}
746+
747+
void Profile(llvm::FoldingSetNodeID &ID) const override {
748+
static int Tag = 0;
749+
ID.AddPointer(&Tag);
750+
ID.AddPointer(RegionOfInterest);
751+
}
752+
753+
private:
754+
/// \return Whether \c RegionOfInterest was modified at \p CurrN compared to
755+
/// the value it holds in \p CallExitBeginN.
756+
bool wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
757+
const ExplodedNode *CallExitBeginN) override;
758+
759+
/// Attempts to find the region of interest in a given record decl,
760+
/// by either following the base classes or fields.
761+
/// Dereferences fields up to a given recursion limit.
762+
/// Note that \p Vec is passed by value, leading to quadratic copying cost,
763+
/// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT.
764+
/// \return A chain fields leading to the region of interest or std::nullopt.
765+
const std::optional<RegionVector>
766+
findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State,
767+
const MemRegion *R, const RegionVector &Vec = {},
768+
int depth = 0);
769+
770+
// Region of interest corresponds to an IVar, exiting a method
771+
// which could have written into that IVar, but did not.
772+
PathDiagnosticPieceRef maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R,
773+
const ObjCMethodCall &Call,
774+
const ExplodedNode *N) final;
775+
776+
PathDiagnosticPieceRef maybeEmitNoteForCXXThis(PathSensitiveBugReport &R,
777+
const CXXConstructorCall &Call,
778+
const ExplodedNode *N) final;
779+
780+
PathDiagnosticPieceRef
781+
maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call,
782+
const ExplodedNode *N) final;
783+
784+
/// Consume the information on the no-store stack frame in order to
785+
/// either emit a note or suppress the report enirely.
786+
/// \return Diagnostics piece for region not modified in the current function,
787+
/// if it decides to emit one.
788+
PathDiagnosticPieceRef
789+
maybeEmitNote(PathSensitiveBugReport &R, const CallEvent &Call,
790+
const ExplodedNode *N, const RegionVector &FieldChain,
791+
const MemRegion *MatchedRegion, StringRef FirstElement,
792+
bool FirstIsReferenceType, unsigned IndirectionLevel);
793+
794+
bool prettyPrintRegionName(const RegionVector &FieldChain,
795+
const MemRegion *MatchedRegion,
796+
StringRef FirstElement, bool FirstIsReferenceType,
797+
unsigned IndirectionLevel,
798+
llvm::raw_svector_ostream &os);
799+
800+
StringRef prettyPrintFirstElement(StringRef FirstElement,
801+
bool MoreItemsExpected,
802+
int IndirectionLevel,
803+
llvm::raw_svector_ostream &os);
804+
};
805+
721806
} // namespace ento
722807
} // namespace clang
723808

clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class CheckerContext {
6969
/// the state of the program before the checker ran. Note, checkers should
7070
/// not retain the node in their state since the nodes might get invalidated.
7171
ExplodedNode *getPredecessor() { return Pred; }
72+
const ProgramPoint getLocation() const { return Location; }
7273
const ProgramStateRef &getState() const { return Pred->getState(); }
7374

7475
/// Check if the checker changed the state of the execution; ex: added

clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class SVal {
8989

9090
SValKind getKind() const { return Kind; }
9191

92+
StringRef getKindStr() const;
93+
9294
// This method is required for using SVal in a FoldingSetNode. It
9395
// extracts a unique signature for this SVal object.
9496
void Profile(llvm::FoldingSetNodeID &ID) const {

clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "clang/Basic/Builtins.h"
1717
#include "clang/Basic/CharInfo.h"
1818
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
19+
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
1920
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
2021
#include "clang/StaticAnalyzer/Core/Checker.h"
2122
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -337,7 +338,8 @@ class CStringChecker : public Checker< eval::Call,
337338
const Stmt *S, StringRef WarningMsg) const;
338339
void emitAdditionOverflowBug(CheckerContext &C, ProgramStateRef State) const;
339340
void emitUninitializedReadBug(CheckerContext &C, ProgramStateRef State,
340-
const Expr *E, StringRef Msg) const;
341+
const Expr *E, const MemRegion *R,
342+
StringRef Msg) const;
341343
ProgramStateRef checkAdditionOverflow(CheckerContext &C,
342344
ProgramStateRef state,
343345
NonLoc left,
@@ -474,7 +476,8 @@ ProgramStateRef CStringChecker::checkInit(CheckerContext &C,
474476
OS << "The first element of the ";
475477
printIdxWithOrdinalSuffix(OS, Buffer.ArgumentIndex + 1);
476478
OS << " argument is undefined";
477-
emitUninitializedReadBug(C, State, Buffer.Expression, OS.str());
479+
emitUninitializedReadBug(C, State, Buffer.Expression,
480+
FirstElementVal->getAsRegion(), OS.str());
478481
return nullptr;
479482
}
480483

@@ -538,7 +541,8 @@ ProgramStateRef CStringChecker::checkInit(CheckerContext &C,
538541
OS << ") in the ";
539542
printIdxWithOrdinalSuffix(OS, Buffer.ArgumentIndex + 1);
540543
OS << " argument is undefined";
541-
emitUninitializedReadBug(C, State, Buffer.Expression, OS.str());
544+
emitUninitializedReadBug(C, State, Buffer.Expression,
545+
FirstElementVal->getAsRegion(), OS.str());
542546
return nullptr;
543547
}
544548
return State;
@@ -818,7 +822,7 @@ void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State,
818822

819823
void CStringChecker::emitUninitializedReadBug(CheckerContext &C,
820824
ProgramStateRef State,
821-
const Expr *E,
825+
const Expr *E, const MemRegion *R,
822826
StringRef Msg) const {
823827
if (ExplodedNode *N = C.generateErrorNode(State)) {
824828
if (!BT_UninitRead)
@@ -831,6 +835,7 @@ void CStringChecker::emitUninitializedReadBug(CheckerContext &C,
831835
Report->getLocation());
832836
Report->addRange(E->getSourceRange());
833837
bugreporter::trackExpressionValue(N, E, *Report);
838+
Report->addVisitor<NoStoreFuncVisitor>(R->castAs<SubRegion>());
834839
C.emitReport(std::move(Report));
835840
}
836841
}

clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -522,95 +522,6 @@ PathDiagnosticPieceRef NoStateChangeFuncVisitor::VisitNode(
522522
return maybeEmitNoteForParameters(R, *Call, N);
523523
}
524524

525-
//===----------------------------------------------------------------------===//
526-
// Implementation of NoStoreFuncVisitor.
527-
//===----------------------------------------------------------------------===//
528-
529-
namespace {
530-
/// Put a diagnostic on return statement of all inlined functions
531-
/// for which the region of interest \p RegionOfInterest was passed into,
532-
/// but not written inside, and it has caused an undefined read or a null
533-
/// pointer dereference outside.
534-
class NoStoreFuncVisitor final : public NoStateChangeFuncVisitor {
535-
const SubRegion *RegionOfInterest;
536-
MemRegionManager &MmrMgr;
537-
const SourceManager &SM;
538-
const PrintingPolicy &PP;
539-
540-
/// Recursion limit for dereferencing fields when looking for the
541-
/// region of interest.
542-
/// The limit of two indicates that we will dereference fields only once.
543-
static const unsigned DEREFERENCE_LIMIT = 2;
544-
545-
using RegionVector = SmallVector<const MemRegion *, 5>;
546-
547-
public:
548-
NoStoreFuncVisitor(const SubRegion *R, bugreporter::TrackingKind TKind)
549-
: NoStateChangeFuncVisitor(TKind), RegionOfInterest(R),
550-
MmrMgr(R->getMemRegionManager()),
551-
SM(MmrMgr.getContext().getSourceManager()),
552-
PP(MmrMgr.getContext().getPrintingPolicy()) {}
553-
554-
void Profile(llvm::FoldingSetNodeID &ID) const override {
555-
static int Tag = 0;
556-
ID.AddPointer(&Tag);
557-
ID.AddPointer(RegionOfInterest);
558-
}
559-
560-
private:
561-
/// \return Whether \c RegionOfInterest was modified at \p CurrN compared to
562-
/// the value it holds in \p CallExitBeginN.
563-
bool wasModifiedBeforeCallExit(const ExplodedNode *CurrN,
564-
const ExplodedNode *CallExitBeginN) override;
565-
566-
/// Attempts to find the region of interest in a given record decl,
567-
/// by either following the base classes or fields.
568-
/// Dereferences fields up to a given recursion limit.
569-
/// Note that \p Vec is passed by value, leading to quadratic copying cost,
570-
/// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT.
571-
/// \return A chain fields leading to the region of interest or std::nullopt.
572-
const std::optional<RegionVector>
573-
findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State,
574-
const MemRegion *R, const RegionVector &Vec = {},
575-
int depth = 0);
576-
577-
// Region of interest corresponds to an IVar, exiting a method
578-
// which could have written into that IVar, but did not.
579-
PathDiagnosticPieceRef maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R,
580-
const ObjCMethodCall &Call,
581-
const ExplodedNode *N) final;
582-
583-
PathDiagnosticPieceRef maybeEmitNoteForCXXThis(PathSensitiveBugReport &R,
584-
const CXXConstructorCall &Call,
585-
const ExplodedNode *N) final;
586-
587-
PathDiagnosticPieceRef
588-
maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call,
589-
const ExplodedNode *N) final;
590-
591-
/// Consume the information on the no-store stack frame in order to
592-
/// either emit a note or suppress the report enirely.
593-
/// \return Diagnostics piece for region not modified in the current function,
594-
/// if it decides to emit one.
595-
PathDiagnosticPieceRef
596-
maybeEmitNote(PathSensitiveBugReport &R, const CallEvent &Call,
597-
const ExplodedNode *N, const RegionVector &FieldChain,
598-
const MemRegion *MatchedRegion, StringRef FirstElement,
599-
bool FirstIsReferenceType, unsigned IndirectionLevel);
600-
601-
bool prettyPrintRegionName(const RegionVector &FieldChain,
602-
const MemRegion *MatchedRegion,
603-
StringRef FirstElement, bool FirstIsReferenceType,
604-
unsigned IndirectionLevel,
605-
llvm::raw_svector_ostream &os);
606-
607-
StringRef prettyPrintFirstElement(StringRef FirstElement,
608-
bool MoreItemsExpected,
609-
int IndirectionLevel,
610-
llvm::raw_svector_ostream &os);
611-
};
612-
} // namespace
613-
614525
/// \return Whether the method declaration \p Parent
615526
/// syntactically has a binary operation writing into the ivar \p Ivar.
616527
static bool potentiallyWritesIntoIvar(const Decl *Parent,

clang/lib/StaticAnalyzer/Core/SVals.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,23 @@ bool SVal::isZeroConstant() const {
263263
// Pretty-Printing.
264264
//===----------------------------------------------------------------------===//
265265

266+
StringRef SVal::getKindStr() const {
267+
switch (getKind()) {
268+
#define BASIC_SVAL(Id, Parent) \
269+
case Id##Kind: \
270+
return #Id;
271+
#define LOC_SVAL(Id, Parent) \
272+
case Loc##Id##Kind: \
273+
return #Id;
274+
#define NONLOC_SVAL(Id, Parent) \
275+
case NonLoc##Id##Kind: \
276+
return #Id;
277+
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.def"
278+
#undef REGION
279+
}
280+
llvm_unreachable("Unkown kind!");
281+
}
282+
266283
LLVM_DUMP_METHOD void SVal::dump() const { dumpToStream(llvm::errs()); }
267284

268285
void SVal::printJson(raw_ostream &Out, bool AddQuotes) const {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %clang_analyze_cc1 -verify %s \
2+
// RUN: -analyzer-checker=core,alpha.unix.cstring \
3+
// RUN: -analyzer-output=text
4+
5+
#include "Inputs/system-header-simulator.h"
6+
7+
// Inspired by a report on ffmpeg, libavcodec/tiertexseqv.c, seq_decode_op1().
8+
int coin();
9+
10+
void maybeWrite(const char *src, unsigned size, int *dst) {
11+
if (coin()) // expected-note{{Assuming the condition is false}}
12+
// expected-note@-1{{Taking false branch}}
13+
memcpy(dst, src, size);
14+
} // expected-note{{Returning without writing to '*dst'}}
15+
16+
void returning_without_writing_to_memcpy(const char *src, unsigned size) {
17+
int block[8 * 8]; // expected-note{{'block' initialized here}}
18+
// expected-note@+1{{Calling 'maybeWrite'}}
19+
maybeWrite(src, size, block); // expected-note{{Returning from 'maybeWrite'}}
20+
21+
int buf[8 * 8];
22+
memcpy(buf, &block[0], 8); // expected-warning{{The first element of the 2nd argument is undefined [alpha.unix.cstring.UninitializedRead]}}
23+
// expected-note@-1{{The first element of the 2nd argument is undefined}}
24+
// expected-note@-2{{Other elements might also be undefined}}
25+
}

0 commit comments

Comments
 (0)