-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[analyzer] Implement binary operations on LazyCompoundVals #106982
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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 enirely. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Just a typo. |
||||||
/// \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 | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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" | ||||||
|
@@ -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, | ||||||
|
@@ -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; | ||||||
} | ||||||
|
||||||
|
@@ -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, | ||||||
FirstElementVal->getAsRegion(), OS.str()); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return nullptr; | ||||||
} | ||||||
return State; | ||||||
|
@@ -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) | ||||||
|
@@ -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)); | ||||||
} | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -315,6 +315,62 @@ static bool isFunctionMacroExpansion(SourceLocation Loc, | |
return EInfo.isFunctionMacroExpansion(); | ||
} | ||
|
||
static const LocationContext *getFirstNonCtorCall(const LocationContext *LCtx) { | ||
while (llvm::isa_and_nonnull<CXXConstructorDecl>(LCtx->getDecl())) | ||
LCtx = LCtx->getParent(); | ||
return LCtx; | ||
} | ||
|
||
static const MemRegion *getInitializerRegion(const PostInitializer &PI) { | ||
return reinterpret_cast<const MemRegion *>(PI.getLocationValue()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reinterpret cast is really scary, but I see that this is apparently the intended way of using It would be good to have a templated |
||
} | ||
|
||
/// Based largely on isInitializationOfVar(). Checks if N initializes FR. | ||
static bool isInitializationOfField(const ExplodedNode *N, | ||
const FieldRegion *FR) { | ||
std::optional<PostInitializer> P = N->getLocationAs<PostInitializer>(); | ||
if (!P) | ||
return false; | ||
|
||
const MemRegion *InitR = getInitializerRegion(*P); | ||
|
||
if (FR != InitR) | ||
return false; | ||
|
||
const MemSpaceRegion *FRSpace = FR->getMemorySpace(); | ||
const auto *FRCtx = dyn_cast<StackSpaceRegion>(FRSpace); | ||
|
||
if (!FRCtx) | ||
return true; | ||
|
||
// The stack frame of N is the constructor, but the FieldRegion is not local | ||
// to the constructor, but rather to the caller function. | ||
const LocationContext *CallerCtx = | ||
getFirstNonCtorCall(N->getLocationContext()); | ||
|
||
return FRCtx->getStackFrame() == CallerCtx->getStackFrame(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like you are calling the same method on two different objects of the same type, but IIUC their types are unrelated: I think it would be helpful to rename |
||
} | ||
|
||
static bool isConstructorCallFor(const SubRegion *RegionOfInterest, | ||
const ExplodedNode *N) { | ||
|
||
if (N->getStackFrame()->inTopFrame()) | ||
return false; | ||
|
||
ProgramStateRef State = N->getState(); | ||
|
||
CallEventRef<> Call = | ||
State->getStateManager().getCallEventManager().getCaller( | ||
N->getStackFrame(), State); | ||
|
||
if (const CXXConstructorCall *CC = dyn_cast<CXXConstructorCall>(Call)) { | ||
if (CC->getCXXThisVal().getAsRegion() == RegionOfInterest) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/// \return Whether \c RegionOfInterest was modified at \p N, | ||
/// where \p ValueAfter is \c RegionOfInterest's value at the end of the | ||
/// stack frame. | ||
|
@@ -324,19 +380,35 @@ static bool wasRegionOfInterestModifiedAt(const SubRegion *RegionOfInterest, | |
ProgramStateRef State = N->getState(); | ||
ProgramStateManager &Mgr = N->getState()->getStateManager(); | ||
|
||
// If the region of interest is constructed right here, it is obviously | ||
// modifying. This gets rid of notes like "Retuning without writing to *this", | ||
// which makes no sense right in the constructor call. | ||
if (isConstructorCallFor(RegionOfInterest, N)) | ||
return true; | ||
|
||
if (!N->getLocationAs<PostStore>() && !N->getLocationAs<PostInitializer>() && | ||
!N->getLocationAs<PostStmt>()) | ||
return false; | ||
|
||
// If we are tracking a field, check if we reached its initialization point | ||
// in an initializer list. | ||
if (const FieldRegion *FR = RegionOfInterest->getAs<FieldRegion>()) | ||
if (isInitializationOfField(N, FR)) | ||
return true; | ||
|
||
// Writing into region of interest. | ||
if (auto PS = N->getLocationAs<PostStmt>()) | ||
if (auto *BO = PS->getStmtAs<BinaryOperator>()) | ||
if (BO->isAssignmentOp() && RegionOfInterest->isSubRegionOf( | ||
N->getSVal(BO->getLHS()).getAsRegion())) | ||
return true; | ||
|
||
// SVal after the state is possibly different. | ||
SVal ValueAtN = N->getState()->getSVal(RegionOfInterest); | ||
|
||
// If ValueAtN and ValueAfter are different, then RegionOfInterest was | ||
// modified. We also need to handle the special case of undefined values, | ||
// we need to check that the other value is not undefined, only then was | ||
// the region modified. | ||
if (!Mgr.getSValBuilder() | ||
.areEqual(State, ValueAtN, ValueAfter) | ||
.isConstrainedTrue() && | ||
|
@@ -526,91 +598,6 @@ PathDiagnosticPieceRef NoStateChangeFuncVisitor::VisitNode( | |
// 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, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider replacing this with
MAX_DEREFERENCE_COUNT = 1
which would be mostly self-documenting. (However, the current name is also OK if you prefer it.)EDIT: Now I see that this is not new code, just moved from elsewhere. Feel free to ignore this if you're not interested.