Function fseek is now evaluated with setting error return value
and error flags.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
We should totally dedicate an error kind of fseek, see (and please respond, if you could to) D75356#inline-689287.
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
492–494 | This seems a bit excessive, we could merge the last two into FSeekError. |
I do not like to make difference between error from fseek and error from any other function that may fail. AnyError can be used to handle every case. After a failed file operation we do not know if it was feof or ferror kind of error. fseek is only special because it can fail additionally without feof or ferror. If the kind of the last operation needs to be known later, it can be saved in the stream state.
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
492–494 | There are 3 cases:
|
Okay, it took me a while, but I think I understand where you're coming from. Lets preserve this enum. I think NoteTags would be a better fit to pinpoint which function left the stream in a state that should be checked before usage. This patch is quite small, could we add one then?
After a failed file operation we do not know if it was feof or ferror kind of error.
Is this true for that many stream operations?
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
492–494 | Sure, but from the point of the analyzer the latter two are the same, isn't it? Its never a good idea to use a stream on which fseek failed without checking. State splits are the most expensive things the analyzer can make, which is why I'm cautious here. |
Probably we need to clarify when to make warnings. Originally I do not wanted a warning after for example failed read, because it is possible to retry the operation. But the standard says that after failed fread the file position is indeterminate, so a new read may get indeterminate values that is a case for warning. The character I/O functions may work not this way so after failed fgetc it may be possible to retry it and no warning is needed. Some operations such as fclose and fseek can be called in error (including EOF) state.
For this to work we need more kinds of error states, or save the type of the last operation in the state (and use it to determine if warning is needed). Probably the best is to have only a single "failed" state and determine what kind of error is possible based on the saved last operation type. A "feof" and "ferror" kind of error state is still needed because after a call to feof if it returned true the error type is fixed to EOF error.
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
492–494 | Somehow, this should be done with just two new states. Maybe there should be an error state "Unknown" instead of OtherError (or FeoFOrFError what I suggested at the other patch) which can be any of the errors plus the NoError value. Later, when ferror() of feof() is checked we can do a second state split, but not earlier. |
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
492–494 | Yes it is possible to use two new states only. For example by having a new FSeekError that can "manifest" in feof, ferror or no error. Better is to have only one "last-operation-failed-with-any-error" state and determine later if needed what errors are possible (based on the last operation). For fseek it is possible to get feof, ferror, or none of these (but still failed fseek). For reading operations the error can be feof or ferror. After write only ferror is possible. |
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
559–560 | Please explain the high-level idea behind this code. |
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
559–560 | The higher level idea is that if a stream function fails we do not create a new state for every type of error (EOF and "other" error). Instead there will be an "unknown error" state. The description for each stream function contains what errors are possible after that function (PossibleErrors). If it is needed to know the exact error (like here, what should feof return?) we look at the previous function to see what errors are possible after it. If EOF is not possible at all, the feof returns false. If EOF is possible and only one other type of error, we make a state split with EOF error and the other error set. If EOF and two possible other errors are possible there is state split again but the non-EOF state contains UnknownError. In PossibleErrors the NoError state is possible. This indicates that the function failed (returned an error value) but the stream error flags are not set (can happen at fseek). There are 3 possible error values (EOF, "other" and no error), if after a feof there is UnknownError we know that the remaining 2 error types are possible. |
I can tell! The patch is amazing, the code practically reads itself aloud. I have some inlines but nothing major, the high level idea is great.
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
98–105 | This feels clunky to me. Suppose that we're curious about the stream reaching EOF. We know that the last operation on the stream could cause EOF and ERROR. This function then would return FError. That doesn't really feel like the answer to the question "isErrorPossible". In what contexts do you see calling this function that isn't like this?: Optional<StreamState::ErrorKindTy> NewError = SS->isErrorPossible(ErrorKind); if (!NewError) { // This kind of error is not possible, function returns zero. // Error state remains unknown. AddTransition(bindInt(0, State, C, CE), StreamState::UnknownError); } else { // Add state with true returned. AddTransition(bindAndAssumeTrue(State, C, CE), ErrorKind); // Add state with false returned and the new remaining error type. AddTransition(bindInt(0, State, C, CE), *NewError); } Would it make more sense to move the logic of creating state transitions into a function instead? | |
536–544 | It took me a while to realize that this also includes the case where the stat is not in any error :) Could you add a line of comment please? | |
553–556 | I gave this plenty of thought, and I now believe that the state split here is appropriate. We really should ensure that the user checked both feof and ferror. | |
clang/test/Analysis/stream-error.c | ||
62 | That is a bit misleading, because Eof isn't EOF as specified in the standard. How about IsEof? |
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
98–105 | The name of the function may be misleading. The non-empty Optional means "yes" and an empty means "no". And if yes additional information is returned in the optional value. If the transitions would be included in this function it needs more parameters. This function is for the part of the operation that computes the remaining error ( getRemainingPossibleError is a better name?). Later it will turn out in what way the function is used and if there is code repetition a new function can be introduced, but not now (the needed state transitions may be different). |
- Rebase
- Improved comments
- Changed UnknownError to Unknown
- Test changes (removed unneeded test)
LGTM! You seem to have a firm grasp on where you want this checker to go, and I like everything about it.
The code needs seme clang-format treatment, and another eye might not hurt, but as far as I'm concerned, I'm super happy how this patch turned out.
clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | ||
---|---|---|
98–105 | getRemainingPossibleError sounds great! You convinced me, if we ever need to stuff more things into this function, we will do that then. |
This feels clunky to me.
Suppose that we're curious about the stream reaching EOF. We know that the last operation on the stream could cause EOF and ERROR. This function then would return FError. That doesn't really feel like the answer to the question "isErrorPossible".
In what contexts do you see calling this function that isn't like this?:
Would it make more sense to move the logic of creating state transitions into a function instead?