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

proposal: bufio: Add finalizer to Scanner that warns if .Err not called #51299

Closed
earthboundkid opened this issue Feb 21, 2022 · 23 comments
Closed

Comments

@earthboundkid
Copy link
Contributor

It is unfortunately common for users to incorrectly use Scanner without calling .Err(). (In particular, I've seen it in three different social media posts recently.) We can't change the API, but we can add a runtime.Finalizer that will print a warning to stderr if a Scanner gets garbage collected with Scanner.scanCalled and Scanner.done true but not (new field I would add) Scanner.errCalled.

@gopherbot gopherbot added this to the Proposal milestone Feb 21, 2022
@earthboundkid
Copy link
Contributor Author

Cf. #17747 which is probably a better idea but more intrusive. In either case, the way to silence the warning is _ = s.Err()

@ianlancetaylor ianlancetaylor added this to Incoming in Proposals (old) Feb 21, 2022
@ianlancetaylor
Copy link
Contributor

CC @robpike

@robpike
Copy link
Contributor

robpike commented Feb 21, 2022

Interesting idea, but isn't this more a vet-check kind of thing?

@ericlagergren
Copy link
Contributor

I do not think the standard library should ever write unprompted warnings to stderr. It means any code parsing stderr (logs, etc.) has to handle these new warnings. And if you throw away stderr then the warning is useless.

@earthboundkid
Copy link
Contributor Author

Go writes to standard error if an http.Handler panics.

@earthboundkid
Copy link
Contributor Author

I’d be happy with #17747. It wouldn’t help people who don’t test their code, but they’re sort of hopeless anyway. The finalizer has the problem that it might not run at all if the program exits before garbage collection.

@ericlagergren
Copy link
Contributor

@carlmjohnson net/http uses ErrorLog, which uses the log package when nil. So, it’s trivially configurable.

The only package I can think of is crypto/rand, and that only occurs on startup and under abnormal conditions.

@seankhliao
Copy link
Member

Having net/http recover panics and print them was considered a mistake: #25245

@earthboundkid
Copy link
Contributor Author

You should only see the warning if your program is incorrectly written.

Thinking more, the real objection is that logging requires package os and bufio should not have it as a requirement.

@ericlagergren
Copy link
Contributor

@carlmjohnson sure, but incorrectly written programs can still run correctly. Perhaps Scan never returns an error or reaching EOF is irrelevant. This proposal would start writing to stderr, potentially causing problems.

It's why Rob's idea of a vet check seems like the correct approach to me: you catch most incorrect uses of the API ahead of time without causing new problems by writing to stderr.

@earthboundkid
Copy link
Contributor Author

#17747 is marked as “Needs Decision”. From the feedback here, it seems to be the preferred option. I would also be happy with it. I’d be glad to close this issue if there is consensus to revive that issue and implement it.

@bcmills
Copy link
Contributor

bcmills commented Feb 22, 2022

A less-invasive alternative here would be to only emit the warning if the Err would have returned a non-nil error. That would prevent spurious output for the small subset of cases where an error really is impossible, but at the cost of missing the diagnostic in the typical case.

(FWIW, I prefer #17747 too.)

@mpx
Copy link
Contributor

mpx commented Feb 24, 2022

Wouldn't mandating checking Err() be a breaking change or cause unwanted side-effects?

There is likely non-buggy code that doesn't need to call Err(). A somewhat contrived example:

func containsLine(rd io.Reader, want string) bool {
  s := bufio.NewScanner(rd)
  for s.Scan() {
    if s.Text() == want {
      return true
    }
  }
  return false
}

Not great code, but potentially fine depending on the context.

I think it would be better to provide an easy to use + hard to misuse replacement API instead. Package docs (and maybe linters) can recommend the better API instead.

@earthboundkid
Copy link
Contributor Author

In the proposed change, it would only trigger the warning if it consumed the reader (Scanner.done == true) and there was an error, and the output would be a stray log to stderr, not a "breaking change" per se.

If someone can think of a better API, I would like to see that proposal, but I can't really imagine what it could do to enforce the error check. If adding vet or logging warnings with bufio.Scanner are deemed too instrusive, there could be bufio.StrictScanner, which is just like bufio.Scanner, except that go vet enforces the error check and maybe there is a panicking finalizer to really drive it home…

@guodongli-google
Copy link

There is likely non-buggy code that doesn't need to call Err(). A somewhat contrived example:

func containsLine(rd io.Reader, want string) bool {
  s := bufio.NewScanner(rd)
  for s.Scan() {
    if s.Text() == want {
      return true
    }
  }
  return false
}

Not great code, but potentially fine depending on the context.

If the function's signature is:

func containsLine(rd io.Reader, want string) (..., error)

then the Err() should be checked for sure. For other cases, it may be still good to check Err() as a defensive programming practice.

@rsc
Copy link
Contributor

rsc commented Mar 2, 2022

The general sentiment here seems to be that printing to stderr at some arbitrary time after the code executes to tell you it forgot to do something is problematic in the standard library, and that we should probably look into the vet check instead.

Do I have that right?

@earthboundkid
Copy link
Contributor Author

I think the vet check is preferable except for the issue of false positives.

Another approach besides bufio.StrictScanner is to add bufio.LaxScanner as a type alias of bufio.Scanner that vet can deliberately ignore.

@rsc rsc moved this from Incoming to Active in Proposals (old) Mar 2, 2022
@rsc
Copy link
Contributor

rsc commented Mar 2, 2022

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@Merovius
Copy link
Contributor

Merovius commented Mar 3, 2022

@carlmjohnson

I think the vet check is preferable except for the issue of false positives.

False positives of the vet check would also (usually) be false positives for printing a warning. I don't think they are worse for vet than here.

@rogpeppe
Copy link
Contributor

rogpeppe commented Mar 3, 2022

FWIW I've often used Scanner on strings.Reader where there's no possibility of error.

"false positives" of the vet check would also (usually) be false positives for printing a warning. I don't think they are worse for vet than here.

They would be worse than if @bcmills' suggestion was implemented, AFAICS, because vet would probably find it harder to detect cases where the underlying reader cannot return an error:

@Merovius
Copy link
Contributor

Merovius commented Mar 3, 2022

@rogpeppe Fair enough.

@rsc rsc moved this from Active to Likely Decline in Proposals (old) Mar 9, 2022
@rsc
Copy link
Contributor

rsc commented Mar 9, 2022

Based on the discussion above, this proposal seems like a likely decline.
— rsc for the proposal review group

@rsc
Copy link
Contributor

rsc commented Mar 16, 2022

No change in consensus, so declined.
— rsc for the proposal review group

@rsc rsc closed this as completed Mar 16, 2022
@golang golang locked and limited conversation to collaborators Mar 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

No branches or pull requests