Skip to content

Commit

Permalink
[clang][analyzer] No end-of-file when seek to file begin.
Browse files Browse the repository at this point in the history
If `fseek` is used with 0 position and SEEK_SET it sets the position
to the start of the file. This should not cause FEOF (end of file) error.
The case of an empty file is not handled for simplification.
It is not exactly defined in what cases `fseek` produces the different
error states. Normally feof should not happen at all because it is
possible to set the position after the end of file, but previous tests
showed that still feof (and any other error cases) can happen.

Reviewed By: donat.nagy

Differential Revision: https://reviews.llvm.org/D153363
  • Loading branch information
balazske committed Jun 30, 2023
1 parent db15ace commit 2eefd19
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 6 deletions.
34 changes: 29 additions & 5 deletions clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,14 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
0}},
};

/// Expanded value of EOF, empty before initialization.
mutable std::optional<int> EofVal;
/// Expanded value of SEEK_SET, 0 if not found.
mutable int SeekSetVal = 0;
/// Expanded value of SEEK_CUR, 1 if not found.
mutable int SeekCurVal = 1;
/// Expanded value of SEEK_END, 2 if not found.
mutable int SeekEndVal = 2;

void evalFopen(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;
Expand Down Expand Up @@ -432,7 +439,7 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
});
}

void initEof(CheckerContext &C) const {
void initMacroValues(CheckerContext &C) const {
if (EofVal)
return;

Expand All @@ -441,6 +448,15 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
EofVal = *OptInt;
else
EofVal = -1;
if (const std::optional<int> OptInt =
tryExpandAsInteger("SEEK_SET", C.getPreprocessor()))
SeekSetVal = *OptInt;
if (const std::optional<int> OptInt =
tryExpandAsInteger("SEEK_END", C.getPreprocessor()))
SeekEndVal = *OptInt;
if (const std::optional<int> OptInt =
tryExpandAsInteger("SEEK_CUR", C.getPreprocessor()))
SeekCurVal = *OptInt;
}

/// Searches for the ExplodedNode where the file descriptor was acquired for
Expand Down Expand Up @@ -488,7 +504,7 @@ const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,

void StreamChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
initEof(C);
initMacroValues(C);

const FnDescription *Desc = lookupFn(Call);
if (!Desc || !Desc->PreFn)
Expand Down Expand Up @@ -786,6 +802,11 @@ void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
if (!State->get<StreamMap>(StreamSym))
return;

const llvm::APSInt *PosV =
C.getSValBuilder().getKnownValue(State, Call.getArgSVal(1));
const llvm::APSInt *WhenceV =
C.getSValBuilder().getKnownValue(State, Call.getArgSVal(2));

DefinedSVal RetVal = makeRetVal(C, CE);

// Make expression result.
Expand All @@ -804,9 +825,12 @@ void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
// It is possible that fseek fails but sets none of the error flags.
// If fseek failed, assume that the file position becomes indeterminate in any
// case.
StreamErrorState NewErrS = ErrorNone | ErrorFError;
// Setting the position to start of file never produces EOF error.
if (!(PosV && *PosV == 0 && WhenceV && *WhenceV == SeekSetVal))
NewErrS = NewErrS | ErrorFEof;
StateFailed = StateFailed->set<StreamMap>(
StreamSym,
StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true));
StreamSym, StreamState::getOpened(Desc, NewErrS, true));

C.addTransition(StateNotFailed);
C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
Expand Down Expand Up @@ -1153,7 +1177,7 @@ StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
return State;

int64_t X = CI->getValue().getSExtValue();
if (X >= 0 && X <= 2)
if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal)
return State;

if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
Expand Down
31 changes: 30 additions & 1 deletion clang/test/Analysis/stream-error.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ void error_fseek(void) {
FILE *F = fopen("file", "r");
if (!F)
return;
int rc = fseek(F, 0, SEEK_SET);
int rc = fseek(F, 1, SEEK_SET);
if (rc) {
int IsFEof = feof(F), IsFError = ferror(F);
// Get feof or ferror or no error.
Expand All @@ -173,6 +173,35 @@ void error_fseek(void) {
fclose(F);
}

void error_fseek_0(void) {
FILE *F = fopen("file", "r");
if (!F)
return;
int rc = fseek(F, 0, SEEK_SET);
if (rc) {
int IsFEof = feof(F), IsFError = ferror(F);
// Get ferror or no error, but not feof.
clang_analyzer_eval(IsFError);
// expected-warning@-1 {{FALSE}}
// expected-warning@-2 {{TRUE}}
clang_analyzer_eval(IsFEof);
// expected-warning@-1 {{FALSE}}
// Error flags should not change.
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
if (IsFError)
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
else
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
} else {
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
// Error flags should not change.
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
}
fclose(F);
}

void error_indeterminate(void) {
FILE *F = fopen("file", "r+");
if (!F)
Expand Down

0 comments on commit 2eefd19

Please sign in to comment.