-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[analyzer] Assume the result of 'fopen' can't alias with 'std{in,out,err}' #100085
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 |
---|---|---|
|
@@ -254,7 +254,8 @@ inline void assertStreamStateOpened(const StreamState *SS) { | |
} | ||
|
||
class StreamChecker : public Checker<check::PreCall, eval::Call, | ||
check::DeadSymbols, check::PointerEscape> { | ||
check::DeadSymbols, check::PointerEscape, | ||
check::ASTDecl<TranslationUnitDecl>> { | ||
BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"}; | ||
BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"}; | ||
BugType BT_UseAfterOpenFailed{this, "Invalid stream", | ||
|
@@ -276,11 +277,21 @@ class StreamChecker : public Checker<check::PreCall, eval::Call, | |
const CallEvent *Call, | ||
PointerEscapeKind Kind) const; | ||
|
||
/// Finds the declarations of 'FILE *stdin, *stdout, *stderr'. | ||
void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager &, | ||
BugReporter &) const; | ||
|
||
const BugType *getBT_StreamEof() const { return &BT_StreamEof; } | ||
const BugType *getBT_IndeterminatePosition() const { | ||
return &BT_IndeterminatePosition; | ||
} | ||
|
||
/// Assumes that the result of 'fopen' can't alias with the pointee of | ||
/// 'stdin', 'stdout' or 'stderr'. | ||
ProgramStateRef assumeNoAliasingWithStdStreams(ProgramStateRef State, | ||
DefinedSVal RetVal, | ||
CheckerContext &C) const; | ||
|
||
const NoteTag *constructSetEofNoteTag(CheckerContext &C, | ||
SymbolRef StreamSym) const { | ||
return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) { | ||
|
@@ -451,6 +462,10 @@ class StreamChecker : public Checker<check::PreCall, eval::Call, | |
/// The built-in va_list type is platform-specific | ||
mutable QualType VaListType; | ||
|
||
mutable const VarDecl *StdinDecl = nullptr; | ||
mutable const VarDecl *StdoutDecl = nullptr; | ||
mutable const VarDecl *StderrDecl = nullptr; | ||
|
||
void evalFopen(const FnDescription *Desc, const CallEvent &Call, | ||
CheckerContext &C) const; | ||
|
||
|
@@ -887,6 +902,30 @@ bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { | |
return C.isDifferent(); | ||
} | ||
|
||
ProgramStateRef StreamChecker::assumeNoAliasingWithStdStreams( | ||
ProgramStateRef State, DefinedSVal RetVal, CheckerContext &C) const { | ||
auto assumeRetNE = [&C, RetVal](ProgramStateRef State, | ||
const VarDecl *Var) -> ProgramStateRef { | ||
if (!Var) | ||
return State; | ||
const auto *LCtx = C.getLocationContext(); | ||
auto &StoreMgr = C.getStoreManager(); | ||
auto &SVB = C.getSValBuilder(); | ||
SVal VarValue = State->getSVal(StoreMgr.getLValueVar(Var, LCtx)); | ||
auto NoAliasState = | ||
SVB.evalBinOp(State, BO_NE, RetVal, VarValue, SVB.getConditionType()) | ||
.castAs<DefinedOrUnknownSVal>(); | ||
return State->assume(NoAliasState, true); | ||
}; | ||
|
||
assert(State); | ||
State = assumeRetNE(State, StdinDecl); | ||
State = assumeRetNE(State, StdoutDecl); | ||
State = assumeRetNE(State, StderrDecl); | ||
assert(State); | ||
return State; | ||
} | ||
|
||
void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, | ||
CheckerContext &C) const { | ||
ProgramStateRef State = C.getState(); | ||
|
@@ -899,6 +938,7 @@ void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, | |
assert(RetSym && "RetVal must be a symbol here."); | ||
|
||
State = State->BindExpr(CE, C.getLocationContext(), RetVal); | ||
State = assumeNoAliasingWithStdStreams(State, RetVal, C); | ||
|
||
// Bifurcate the state into two: one with a valid FILE* pointer, the other | ||
// with a NULL. | ||
|
@@ -2017,6 +2057,36 @@ ProgramStateRef StreamChecker::checkPointerEscape( | |
return State; | ||
} | ||
|
||
static const VarDecl * | ||
getGlobalStreamPointerByName(const TranslationUnitDecl *TU, StringRef VarName) { | ||
ASTContext &Ctx = TU->getASTContext(); | ||
const auto &SM = Ctx.getSourceManager(); | ||
const QualType FileTy = Ctx.getFILEType(); | ||
|
||
if (FileTy.isNull()) | ||
return nullptr; | ||
|
||
const QualType FilePtrTy = Ctx.getPointerType(FileTy).getCanonicalType(); | ||
|
||
auto LookupRes = TU->lookup(&Ctx.Idents.get(VarName)); | ||
for (const Decl *D : LookupRes) { | ||
if (auto *VD = dyn_cast_or_null<VarDecl>(D)) { | ||
if (SM.isInSystemHeader(VD->getLocation()) && VD->hasExternalStorage() && | ||
VD->getType().getCanonicalType() == FilePtrTy) { | ||
return VD; | ||
} | ||
} | ||
} | ||
return nullptr; | ||
} | ||
|
||
void StreamChecker::checkASTDecl(const TranslationUnitDecl *TU, | ||
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. The checker has already initialization functions in 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. Yea, this makes sense to harmonize. I'll try to come back to this one later, or write a good first issue for cleaning this up. |
||
AnalysisManager &, BugReporter &) const { | ||
StdinDecl = getGlobalStreamPointerByName(TU, "stdin"); | ||
StdoutDecl = getGlobalStreamPointerByName(TU, "stdout"); | ||
StderrDecl = getGlobalStreamPointerByName(TU, "stderr"); | ||
} | ||
|
||
//===----------------------------------------------------------------------===// | ||
// Checker registration. | ||
//===----------------------------------------------------------------------===// | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -498,3 +498,15 @@ void gh_93408_regression_ZeroSized(struct ZeroSized *buffer) { | |
fread(buffer, 1, 1, f); // expected-warning {{Stream pointer might be NULL}} no-crash | ||
fclose(f); | ||
} | ||
|
||
extern FILE *stdout_like_ptr; | ||
void no_aliasing(void) { | ||
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 test could have a more meaningful name. 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. I think nobody will ever come back to this test. I don't think I'll elaborate the name. The content should be enough already. |
||
FILE *f = fopen("file", "r"); | ||
clang_analyzer_eval(f == stdin); // expected-warning {{FALSE}} no-TRUE | ||
clang_analyzer_eval(f == stdout); // expected-warning {{FALSE}} no-TRUE | ||
clang_analyzer_eval(f == stderr); // expected-warning {{FALSE}} no-TRUE | ||
clang_analyzer_eval(f == stdout_like_ptr); // expected-warning {{FALSE}} expected-warning {{TRUE}} | ||
if (f && f != stdout) { | ||
fclose(f); | ||
} | ||
} // no-leak: 'fclose()' is always called because 'f' cannot be 'stdout'. |
Uh oh!
There was an error while loading. Please reload this page.