-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[analyzer] Refine invalidation caused by fread
#93408
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 8 commits
f9e841d
c46aa42
79ea47a
0ab63f1
f9142be
034d1a1
c8e5f54
cf90c7b
5608f46
dd82437
0fdf2e5
06f1b6e
a0e5215
17f63d1
0f045b2
5f73d42
dd4d268
c9268ab
b345554
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 |
---|---|---|
|
@@ -717,18 +717,56 @@ const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, | |
return nullptr; | ||
} | ||
|
||
static std::optional<int64_t> getKnownValue(ProgramStateRef State, SVal V) { | ||
SValBuilder &SVB = State->getStateManager().getSValBuilder(); | ||
if (const llvm::APSInt *Int = SVB.getKnownValue(State, V)) | ||
return Int->tryExtValue(); | ||
return std::nullopt; | ||
} | ||
|
||
/// Invalidate only the requested elements instead of the whole buffer. | ||
/// This is basically a refinement of the more generic 'escapeArgs' or | ||
/// the plain old 'invalidateRegions'. | ||
static ProgramStateRef | ||
escapeByStartIndexAndCount(ProgramStateRef State, const CallEvent &Call, | ||
unsigned BlockCount, const SubRegion *Buffer, | ||
QualType ElemType, int64_t StartIndex, | ||
int64_t ElementCount) { | ||
constexpr auto DoNotInvalidateSuperRegion = | ||
RegionAndSymbolInvalidationTraits::InvalidationKinds:: | ||
TK_DoNotInvalidateSuperRegion; | ||
|
||
const LocationContext *LCtx = Call.getLocationContext(); | ||
const ASTContext &Ctx = State->getStateManager().getContext(); | ||
SValBuilder &SVB = State->getStateManager().getSValBuilder(); | ||
auto &RegionManager = Buffer->getMemRegionManager(); | ||
|
||
SmallVector<SVal> EscapingVals; | ||
EscapingVals.reserve(ElementCount); | ||
|
||
RegionAndSymbolInvalidationTraits ITraits; | ||
for (auto Idx : llvm::seq(StartIndex, StartIndex + ElementCount)) { | ||
NonLoc Index = SVB.makeArrayIndex(Idx); | ||
const auto *Element = | ||
RegionManager.getElementRegion(ElemType, Index, Buffer, Ctx); | ||
EscapingVals.push_back(loc::MemRegionVal(Element)); | ||
ITraits.setTrait(Element, DoNotInvalidateSuperRegion); | ||
} | ||
return State->invalidateRegions( | ||
EscapingVals, Call.getOriginExpr(), BlockCount, LCtx, | ||
/*CausesPointerEscape=*/false, | ||
/*InvalidatedSymbols=*/nullptr, &Call, &ITraits); | ||
} | ||
|
||
static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C, | ||
const CallEvent &Call, | ||
ArrayRef<unsigned int> EscapingArgs) { | ||
const auto *CE = Call.getOriginExpr(); | ||
|
||
SmallVector<SVal> EscapingVals; | ||
EscapingVals.reserve(EscapingArgs.size()); | ||
for (auto EscArgIdx : EscapingArgs) | ||
EscapingVals.push_back(Call.getArgSVal(EscArgIdx)); | ||
State = State->invalidateRegions(EscapingVals, CE, C.blockCount(), | ||
C.getLocationContext(), | ||
/*CausesPointerEscape=*/false); | ||
auto GetArgSVal = [&Call](int Idx) { return Call.getArgSVal(Idx); }; | ||
auto EscapingVals = to_vector(map_range(EscapingArgs, GetArgSVal)); | ||
State = State->invalidateRegions(EscapingVals, Call.getOriginExpr(), | ||
C.blockCount(), C.getLocationContext(), | ||
/*CausesPointerEscape=*/false, | ||
/*InvalidatedSymbols=*/nullptr); | ||
return State; | ||
} | ||
|
||
|
@@ -907,6 +945,73 @@ void StreamChecker::preWrite(const FnDescription *Desc, const CallEvent &Call, | |
C.addTransition(State); | ||
} | ||
|
||
static std::optional<QualType> getPointeeType(const MemRegion *R) { | ||
if (!R) | ||
return std::nullopt; | ||
if (const auto *ER = dyn_cast<ElementRegion>(R)) | ||
return ER->getElementType(); | ||
if (const auto *TR = dyn_cast<TypedValueRegion>(R)) | ||
return TR->getValueType(); | ||
if (const auto *SR = dyn_cast<SymbolicRegion>(R)) | ||
return SR->getPointeeStaticType(); | ||
return std::nullopt; | ||
} | ||
|
||
static std::optional<NonLoc> getStartIndex(SValBuilder &SVB, | ||
const MemRegion *R) { | ||
if (!R) | ||
return std::nullopt; | ||
|
||
auto Zero = [&SVB] { | ||
BasicValueFactory &BVF = SVB.getBasicValueFactory(); | ||
return nonloc::ConcreteInt(BVF.getIntValue(0, /*isUnsigned=*/false)); | ||
}; | ||
|
||
if (const auto *ER = dyn_cast<ElementRegion>(R)) | ||
return ER->getIndex(); | ||
if (const auto *TR = dyn_cast<TypedValueRegion>(R)) | ||
steakhal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return Zero(); | ||
if (const auto *SR = dyn_cast<SymbolicRegion>(R)) | ||
return Zero(); | ||
return std::nullopt; | ||
} | ||
|
||
static ProgramStateRef | ||
tryToInvalidateFReadBufferByElements(ProgramStateRef State, CheckerContext &C, | ||
const CallEvent &Call, NonLoc SizeVal, | ||
NonLoc NMembVal) { | ||
// Try to invalidate the individual elements. | ||
const auto *Buffer = | ||
dyn_cast_or_null<SubRegion>(Call.getArgSVal(0).getAsRegion()); | ||
|
||
std::optional<QualType> ElemTy = getPointeeType(Buffer); | ||
std::optional<SVal> StartElementIndex = | ||
getStartIndex(C.getSValBuilder(), Buffer); | ||
|
||
// Drop the outermost ElementRegion to get the buffer. | ||
if (const auto *ER = dyn_cast_or_null<ElementRegion>(Buffer)) | ||
Buffer = dyn_cast<SubRegion>(ER->getSuperRegion()); | ||
|
||
std::optional<int64_t> CountVal = getKnownValue(State, NMembVal); | ||
std::optional<int64_t> Size = getKnownValue(State, SizeVal); | ||
std::optional<int64_t> StartIndexVal = | ||
getKnownValue(State, StartElementIndex.value_or(UnknownVal())); | ||
|
||
if (ElemTy && CountVal && Size && StartIndexVal) { | ||
int64_t NumBytesRead = Size.value() * CountVal.value(); | ||
int64_t ElemSizeInChars = | ||
C.getASTContext().getTypeSizeInChars(*ElemTy).getQuantity(); | ||
bool DivisibleAccessSpan = (NumBytesRead % ElemSizeInChars) == 0; | ||
int64_t NumElementsRead = NumBytesRead / ElemSizeInChars; | ||
constexpr int MaxInvalidatedElementsLimit = 64; | ||
if (DivisibleAccessSpan && NumElementsRead <= MaxInvalidatedElementsLimit) { | ||
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. Is it possible to round the 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. Applied a patch to this code such that the last partial element is considered as a full access. |
||
return escapeByStartIndexAndCount(State, Call, C.blockCount(), Buffer, | ||
*ElemTy, *StartIndexVal, *CountVal); | ||
} | ||
} | ||
return nullptr; | ||
} | ||
|
||
void StreamChecker::evalFreadFwrite(const FnDescription *Desc, | ||
const CallEvent &Call, CheckerContext &C, | ||
bool IsFread) const { | ||
|
@@ -937,8 +1042,14 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc, | |
|
||
// At read, invalidate the buffer in any case of error or success, | ||
// except if EOF was already present. | ||
if (IsFread && !E.isStreamEof()) | ||
State = escapeArgs(State, C, Call, {0}); | ||
if (IsFread && !E.isStreamEof()) { | ||
// Try to invalidate the individual elements. | ||
// Otherwise just fall back to invalidating the whole buffer. | ||
ProgramStateRef InvalidatedState = tryToInvalidateFReadBufferByElements( | ||
State, C, Call, *SizeVal, *NMembVal); | ||
State = | ||
InvalidatedState ? InvalidatedState : escapeArgs(State, C, Call, {0}); | ||
} | ||
|
||
// Generate a transition for the success state. | ||
// If we know the state to be FEOF at fread, do not add a success state. | ||
|
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.
Is it not better to store the
Zero
value in a variable instead of using this lambda?Uh oh!
There was an error while loading. Please reload this page.
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.
This way it's lazily evaluated. I wanted to avoid sideffects inside BVF and it seemed like a plausible way achieving it.