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

Fix panic that can occur when interpreting options in lenient mode #331

Merged
merged 2 commits into from
Aug 20, 2024

Conversation

jhump
Copy link
Member

@jhump jhump commented Aug 19, 2024

The panic fix is tiny. But this PR is not because other changes/improvements were called for: most of the code changes are to improve error handling when in lenient mode. After I fixed the panic, the test case was failing in a different way, due to an issue with how errors (even in lenient mode) were still being passed to an error reporter and causing the stage to fail.

This issue was introduced in #279. That PR moved things around, pushing the responsibility of calling interp.reporter.HandleError down, so that interpreting a single option could potentially report multiple errors (instead of failing fast and only reporting the first).

But when in lenient mode, we don't actually want to send those errors to the reporter: sending the error to the reporter meant the error would get memoized and then returned in subsequent handle calls, which could cause the process to fail when it should be lenient and also can cause it to report the wrong error in lenient mode. So now we demarcate the parts of the process where errors are tolerated (i.e. where lenience mode is actually activated) with a new field on the interpreter that is examined when errors are reported.

This will resolve jhump/protoreflect#620.

Comment on lines 59 to 65
reporter internal.ErrorHandler
lenient bool

// lenienceEnabled is set to true when errors reported to reporter
// should be lenient
lenienceEnabled bool
lenientErrReported bool
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have liked to have formatted this with less churn in the diff. But then I got govet lint warnings based on the struct size being inflated due to bool fields in the middle of the struct. So I moved them all to the end to get rid of that lint issue.

options/options.go Outdated Show resolved Hide resolved
pathBuffer []int32

reporter internal.ErrorHandler
lenient bool
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to rename some of these fields if you think that would help readability. The existing lenient flag means the interpreter will tolerate certain kinds of errors. The new flag below is set to true around the parts of the code that can generate those kinds of errors. (So errors can still occur in lenient mode -- it's just certain kind of errors that lenient mode tolerates.)

Comment on lines +355 to +357
interp.enableLenience(true)
err := interp.interpretFieldPseudoOptions(fqn, fld, opts)
interp.enableLenience(false)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how we indicate the operations in which errors are tolerated.

Comment on lines 365 to 385
if err != nil && !interp.lenient {
if index, err := internal.FindOption(interp.file, interp.reporter, scope, uo, "json_name"); err != nil {
return err
}
if index >= 0 {
} else if index >= 0 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This simple reformatting is for symmetry/consistency with the block below.

@@ -389,7 +406,7 @@ func (interp *interpreter) interpretFieldPseudoOptions(fqn string, fld *descript
}

// and process default pseudo-option
if index, err := interp.processDefaultOption(scope, fqn, fld, uo); err != nil && !interp.lenient {
if index, err := interp.processDefaultOption(scope, fqn, fld, uo); err != nil {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer check interp.lenient here (or above) because when lenience is enabled, the returned err will now always be nil.

Comment on lines +656 to +659
if interp.lenientErrReported {
remain = append(remain, uo)
continue
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When lenience is enabled, the above error is always nil, but we still need to know if any error was reported, so we know whether we need to keep this option in the uninterpreted set.

Comment on lines +1013 to +1017
if srcInfo == nil {
// can happen if we are lenient when interpreting -- this node
// could not be interpreted and thus has no source info; skip
return true
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THIS is the actual fix for the panic. In the cases where srcInfo was nil here, it would trigger a nil-dereference panic on the next line, to look at the Path field. Oops.

@@ -244,6 +254,25 @@ func TestOptionsInUnlinkedFiles(t *testing.T) {
}
}

func TestOptionsInUnlinkedFileInvalid(t *testing.T) {
Copy link
Member Author

@jhump jhump Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a repro test case. It differs from the ones above because it actually is expected to return an error, even in lenient mode (since the error is not related to an unresolved option or type error). This would previously trigger the panic. And then, after the panic was fixed, it would return the wrong error without the other changes in this branch.

Comment on lines +42 to +47
var ewp ErrorWithPos
if errors.As(err, &ewp) {
// replace existing position with given one
return &errorWithSpan{SourceSpan: span, underlying: ewp.Unwrap()}
}
return &errorWithSpan{SourceSpan: span, underlying: err}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some minor changes/improvements throughout this package, for better consistency.

@jhump jhump requested a review from emcfarlane August 19, 2024 15:50
options/options.go Outdated Show resolved Hide resolved
options/options.go Outdated Show resolved Hide resolved
options/options.go Outdated Show resolved Hide resolved
options/options.go Outdated Show resolved Hide resolved
@jhump jhump merged commit 0142a07 into main Aug 20, 2024
8 checks passed
@jhump jhump deleted the jh/fix-panic-and-options-lenient-err-handling branch August 20, 2024 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ParseFilesButDoNotLink panics in option validation with invalid .proto
2 participants