This is an archive of the discontinued LLVM Phabricator instance.

[Analyzer][StreamChecker] Added support for 'fread' and 'fwrite'.
ClosedPublic

Authored by balazske on May 15 2020, 8:39 AM.

Details

Summary

Stream functions fread and fwrite are evaluated
and preconditions checked.
A new bug type is added for a (non fatal) warning if fread
is called in EOF state.

Diff Detail

Event Timeline

balazske created this revision.May 15 2020, 8:39 AM

I think the warning about EOF could be a separate patch, and it could be implemented for most stream operations. The patch in large looks great, but I'm just not sure why we handle fwrite so differently. Could you please explain?

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
55

How about return !(*this == ES)?

453–460

Do we not want this for fwrite as well?

492

// The standard refers to this parameter as "nmemb", but almost everywhere else its called "count".

520

Aaaah okay it took me a while. I think SS->ErrorState != ErrorFEof isn't as talkative as isConstrained.*, could you add just a line like "if we know the state to be EOF..."?

On another note, why is fwrite so special in this context?

balazske marked 4 inline comments as done.May 18 2020, 7:45 AM

The difference of fread and fwrite comes from the fact that fwrite can always succeed, fread does not succeed in EOF state.
The plan is to add the file functions sequentially. From the currently implemented functions only fread needs the warning in EOF state. When any later function is added that fails in EOF state, the warning should be added for that function at that time.

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
55

Probably better (does not look better but is more bug-safe).

453–460

It should be no problem to write into a file at the end of the file, the file is extended.

492

I looked up the functions in cppreference.com, there it is called count. But probably it is better to use the standard name here?

520

If it is an fwrite, the function call can always succeed (even after a previously EOF or error).
If it is an fread, it can succeed but not after an exactly-EOF state.

A success transition is not needed in an fread with exactly EOF condition. (If we have fread with non-exactly EOF, for example ErrorFEof and ErrorFError, the success is needed because the read in ErrorFError can succeed.)

The difference of fread and fwrite comes from the fact that fwrite can always succeed, fread does not succeed in EOF state.
The plan is to add the file functions sequentially. From the currently implemented functions only fread needs the warning in EOF state. When any later function is added that fails in EOF state, the warning should be added for that function at that time.

Ah, silly me. Like, obviously, where would you like write the file, if not at the end of it...

In any case, the warning would be a better fit as a standalone revision because FERROR also deserves a similar warning I think, not to mention that plenty of other stream operations should check against this.

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
492

Its not a big deal, either a comment or a variable name change should be fine. I'll leave it to your judgement, but its not a bad idea to leave a line about the most commonly referred name ("count") is not the same as the standard one ("nmemb").

But just to assure you, this patch is practically perfect. I don't think we're going to have any hiccups moving forward with this. Again, thank you for all the patience!

If the unit of the change is adding fread and fwrite completely, the warning with FEOF at fread is correct to add because it belongs to fread. I was planning to add some file handling functions to the checker that have a basic functionality implemented. In this case, it is the fread-fwrite functions, but the "indeterminate file position" is still missing here (that is a bigger change), so it is the "basic functionality" that is added. The FEOF warning is probably too small to add it in a separate patch, also it is added only to one place now.

There is intentionally no warning about ferror state, first reason is that often ferror and "indeterminate file position" comes together and then the warning will be produced for "indeterminate position" in the next change. Next reason is that ferror in itself is not always cause for warning and we can currently not decide about it. There was the example about write into a file that is open in read mode that results in a ferror state, then the next read should work (with ferror state at start). Another reason, the program should still always handle if a file operation fails. If an operation starts in ferror state, it will probably fail again but that error should be handled by the application.

Intent of these warnings is not to check that the error case was handled by the user code, that is a more difficult thing. We could make a new warning that warns if between two possible failing operations no ferror or feof was called, this would check if the user handled the error. But not accurate because the error can be handled based on the return value of the failed function, without calling ferror or feof (except some special cases like the fgetc). The only safe warning is to add if a function is called in really unuseful way, for example fread in already FEOF state that do never succeed, and the "indeterminate file position" case.

My goal with these changes was to have a check for the CERT FIO34-C case, not for the complete StreamChecker that could contain many other features. So probably not even all file handling functions will be added for now.

balazske updated this revision to Diff 264843.May 19 2020, 4:01 AM
balazske marked 5 inline comments as done.

Renamed "Count" to "NMemb", fixed operator !=.

Szelethus accepted this revision.May 19 2020, 5:22 AM

This patch is great. LGTM!

If the unit of the change is adding fread and fwrite completely, the warning with FEOF at fread is correct to add because it belongs to fread. I was planning to add some file handling functions to the checker that have a basic functionality implemented. In this case, it is the fread-fwrite functions, but the "indeterminate file position" is still missing here (that is a bigger change), so it is the "basic functionality" that is added. The FEOF warning is probably too small to add it in a separate patch, also it is added only to one place now.

Very well, I'm sold. I didn't check that we don't really model another function that needs it. Woops :^)

There is intentionally no warning about ferror state, first reason is that often ferror and "indeterminate file position" comes together and then the warning will be produced for "indeterminate position" in the next change. Next reason is that ferror in itself is not always cause for warning and we can currently not decide about it. There was the example about write into a file that is open in read mode that results in a ferror state, then the next read should work (with ferror state at start). Another reason, the program should still always handle if a file operation fails. If an operation starts in ferror state, it will probably fail again but that error should be handled by the application.

Intent of these warnings is not to check that the error case was handled by the user code, that is a more difficult thing. We could make a new warning that warns if between two possible failing operations no ferror or feof was called, this would check if the user handled the error. But not accurate because the error can be handled based on the return value of the failed function, without calling ferror or feof (except some special cases like the fgetc). The only safe warning is to add if a function is called in really unuseful way, for example fread in already FEOF state that do never succeed, and the "indeterminate file position" case.

The question is what is a good practice on ferror -- a check on errno and then a clearerr? In any case, you are right, this another discussion for another time.

My goal with these changes was to have a check for the CERT FIO34-C case, not for the complete StreamChecker that could contain many other features. So probably not even all file handling functions will be added for now.

Sure, shoot me down if you feel I'm trying to dictate the direction of your project! :)

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
520

Aaaah okay it took me a while. I think SS->ErrorState != ErrorFEof isn't as talkative as isConstrained.*, could you add just a line like "if we know the state to be EOF..."?

Could you please add these few words before commiting?

This revision is now accepted and ready to land.May 19 2020, 5:22 AM
This revision was automatically updated to reflect the committed changes.
balazske marked 2 inline comments as done.May 20 2020, 1:28 AM

I want to test the StreamChecker for false positives. Page http://clang.llvm.org/analyzer/open_projects.html says that it has too much false positives because state splitting (I did not see this page before.)

There is an alternative way of implementation: Store a (ordered) list of previous stream operations and data what we know about the operation. This data contains if the operation failed or not, the symbol that is the return value of the function, error state before and after the operation, what bug report was made already. This list can be populated during analysis, without state splits. At every time when a new information is put into the list it is possible to check for problems.

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
520

It is now "If we know the state to be FEOF at fread, do not add a success state.".