Skip to content
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

[Analyzer Proposal]: Analyzer/fixer for converting Stream.Read calls to ReadAtLeast and ReadExactly #69159

Closed
eerhardt opened this issue May 10, 2022 · 16 comments · Fixed by dotnet/roslyn-analyzers#7208
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.IO code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@eerhardt
Copy link
Member

With the addition of the new Stream ReadAtLeast and ReadExactly methods, there is a pattern of mistakes we can catch with an analyzer and suggest the caller use the new APIs. For example:

stream.Read(buffer, 0, buffer.Length);

where the return value isn't checked, and recommend it be changed to a call to ReadExactly.

Or cases where the return value is checked but required to be the same as what was requested:

if (stream.Read(buffer, 0, buffer.Length) != buffer.Length) throw ...;

and similarly suggest it use ReadExactly.

If the caller is using a length that is different than the length of the buffer being passed in, and not checking the return value, we can suggest to use ReadAtLeast:

stream.Read(buffer, 0, count);

would become:

stream.ReadAtLeast(buffer, count);

However, note in the ReadAtLeast case, the caller should still be checking the return value because the ReadAtLeast call could be returning more than count bytes, and this data would be missed if the caller only consumed count bytes from buffer.

One problem with this analyzer might be when we implement #34098. If the caller saw the "Do not discard results of methods marked as DoNotIgnoreReturnValue" warning first and addressed it, they would lose out on this fixer.

cc @stephentoub

@eerhardt eerhardt added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.IO code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer labels May 10, 2022
@ghost ghost added the untriaged New issue has not been triaged by the area owner label May 10, 2022
@ghost
Copy link

ghost commented May 10, 2022

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

With the addition of the new Stream ReadAtLeast and ReadExactly methods, there is a pattern of mistakes we can catch with an analyzer and suggest the caller use the new APIs. For example:

stream.Read(buffer, 0, buffer.Length);

where the return value isn't checked, and recommend it be changed to a call to ReadExactly.

Or cases where the return value is checked but required to be the same as what was requested:

if (stream.Read(buffer, 0, buffer.Length) != buffer.Length) throw ...;

and similarly suggest it use ReadExactly.

If the caller is using a length that is different than the length of the buffer being passed in, and not checking the return value, we can suggest to use ReadAtLeast:

stream.Read(buffer, 0, count);

would become:

stream.ReadAtLeast(buffer, count);

However, note in the ReadAtLeast case, the caller should still be checking the return value because the ReadAtLeast call could be returning more than count bytes, and this data would be missed if the caller only consumed count bytes from buffer.

One problem with this analyzer might be when we implement #34098. If the caller saw the "Do not discard results of methods marked as DoNotIgnoreReturnValue" warning first and addressed it, they would lose out on this fixer.

cc @stephentoub

Author: eerhardt
Assignees: -
Labels:

api-suggestion, area-System.IO, code-analyzer, code-fixer

Milestone: -

@adamsitnik adamsitnik removed the untriaged New issue has not been triaged by the area owner label May 11, 2022
@adamsitnik adamsitnik added this to the 7.0.0 milestone May 11, 2022
@weltkante

This comment was marked as outdated.

@carlossanlop carlossanlop modified the milestones: 7.0.0, 8.0.0 Jul 6, 2022
@danmoseley
Copy link
Member

danmoseley commented Sep 15, 2022

@jeffhandley @stephentoub with 3.1 EOL in 3 months, and based on some of the issues posted here recently, we can expect a number of folks migrating before support expires. Some of them will hit this and get a hang in their app. It might take a while to connect to the breaking change notice because there isn't an obvious "smell" (the doc doesn't contain the word "hang" for example). Once they've made that connection, they may have many places in their codebase that need auditing for the bug and fixing.

An analyzer/fixer could help with both those hurdles. I wonder whether we should start now to front load getting one into the SDK ..

@stephentoub
Copy link
Member

The intent had been that this be addressed in .NET 7. It unfortunately got pushed out due to lack of time. We should tackle it soon.

@eerhardt
Copy link
Member Author

eerhardt commented Sep 15, 2022

Note that another analyzer that would help in this case is #34098. Code that doesn't capture the result of Stream.Read or BinaryReader.Read would get flagged by that analyzer as well (along with other APIs that shouldn't have their results ignored).

However, the downfall of #34098 is that the necessary attribute didn't get added to net7.0, so we can't mark the APIs as "do not discard" yet.

@buyaa-n
Copy link
Contributor

buyaa-n commented Nov 16, 2022

Estimates:

  • Analyzer: Medium
  • Fixer: Medium

@bartonjs
Copy link
Member

bartonjs commented Mar 7, 2023

Video

Looks good as proposed.

The diagnostic (possibly with a different diagnostic ID) should still fire on older TFMs to report that the call made to Read is unreliable as written.

Category: Reliability
Severity: Warning

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Mar 7, 2023
@buyaa-n buyaa-n added the help wanted [up-for-grabs] Good issue for external contributors label May 4, 2023
@adamsitnik adamsitnik modified the milestones: 8.0.0, 9.0.0 Jul 31, 2023
@mpidash
Copy link
Contributor

mpidash commented Feb 19, 2024

I would like to be assigned to this one, please.

@stephentoub
Copy link
Member

I would like to be assigned to this one, please.

Terrific. Done. Thanks!

@mpidash
Copy link
Contributor

mpidash commented Feb 20, 2024

What is the reason for suggesting to call ReadAtLeast instead of ReadExactly when the length is different from the buffer size?
Is it due to the difference in how the end of the stream is handled in ReadAtLeast (but wouldn't this also apply to the first case)?

@stephentoub
Copy link
Member

What is the reason for suggesting to call ReadAtLeast instead of ReadExactly when the length is different from the buffer size? Is it due to the difference in how the end of the stream is handled in ReadAtLeast (but wouldn't this also apply to the first case)?

I'm actually not sure now... @eerhardt, do you remember what you had in mind there?

@mpidash
Copy link
Contributor

mpidash commented Feb 22, 2024

@stephentoub I think the initial post is based on a comment from you when discussing the implementation of ReadExactly/ReadAtLeast.

One of the reasons why I am following up on the second code fix is because I am not sure how we can enforce the following:

However, note in the ReadAtLeast case, the caller should still be checking the return value because the ReadAtLeast call could be returning more than count bytes, and this data would be missed if the caller only consumed count bytes from buffer.

The code is still unreliable (in a different way) if we just change the call to ReadAtLeast. We could generate something like var bytesRead = stream.ReadAtLeast(buffer, count); to encourage the user to use the return value.

@weltkante
Copy link
Contributor

weltkante commented Feb 22, 2024

in fact, if I read this right, if you change from Read to ReadAtLeast you may introduce additional bugs because now you can overwrite data past the original count being passed in? it might be best to always suggest ReadExactly when the return value isn't checked in the first place? maybe the description of the diagnostic can mention the other options

@eerhardt
Copy link
Member Author

Thinking about this now, I think ReadExactly is the only correct suggestion here. It's the only method you can call without checking the return value, as it is the only method that ensures you got exactly count bytes. When calling ReadAtLeast, you still need to check the return value in case of EOS (when throwOnEndOfStream = false), or when more data is read than minimumBytes.

@stephentoub
Copy link
Member

Yeah, for the life of me I can't think of why I wrote ReadAtLeast in my original comment. It should be ReadExact.

@mpidash
Copy link
Contributor

mpidash commented Feb 22, 2024

Thank you all for helping to clarify 😃.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.IO code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants