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

Don't silently swallow exceptions when verifying #488

Merged
merged 9 commits into from
Jan 27, 2022

Conversation

atomb
Copy link
Collaborator

@atomb atomb commented Jan 21, 2022

(Edited to address my own comment below).

Re-throw exceptions that occur during the split and verify worker, rather than silently swallowing them. Avoids printing "valid" when exceptions occur during solver interaction.

Addresses #487.

When verifying an implementation, set the initial value of the outcome
to `Uninitialized` (a new alternative) instead of `Correct` to avoid
spurious verification success when exceptions occur during solver
interaction.
@atomb
Copy link
Collaborator Author

atomb commented Jan 22, 2022

An alternative (or additional?) approach could be to address what I think is perhaps a separate part of the problem:

async Task DoWork(Split split)
{
var checker = await split.parent.CheckerPool.FindCheckerFor(split.parent, split);
try {
StartCheck(split, checker);
await split.ProverTask;
await ProcessResult(split);
}
catch {
split.ReleaseChecker();
}
}

This releases the checker silently if any exception occurs during checking, without indicating that the proof obligation being checked has not yet been established.

@atomb
Copy link
Collaborator Author

atomb commented Jan 22, 2022

I'm not sure this is ready to go yet, but I'm adding reviewers in the hope of identifying the correct solution.

@atomb atomb changed the title Allow VCGen outcome to be uninitialized Don't silently swallow exceptions when verifying Jan 24, 2022
@atomb
Copy link
Collaborator Author

atomb commented Jan 24, 2022

The current fix now seems pretty reasonable to me, and it's very small. It does cause two current tests to fail because the expected error gets caught in a different place, leading to different output.

@atomb atomb requested a review from rakamaric January 25, 2022 01:16
Source/ExecutionEngine/ExecutionEngine.cs Outdated Show resolved Hide resolved
Source/VCGeneration/SplitAndVerifyWorker.cs Outdated Show resolved Hide resolved
@@ -1244,6 +1244,13 @@ private static void CleanupCheckers(string requestId)
verificationResult.Errors = null;
verificationResult.Outcome = VCGen.Outcome.Inconclusive;
}
catch(AggregateException e)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we catch something more specific? I wouldn't want to swallow any bugs here but catching a bad solver argument seems good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's nothing below AggregateException in the class hierarchy, AFAIK, though we could pull out the wrapped exceptions and behave differently depending on what they are. This code will always lead to a message and an inconclusive result, so I wouldn't expect it to hide bugs. It could be that, after some experience with it, we realize that it would be best to handle different exceptions differently, but at the moment I'm not sure what the different behavior would be.

At the moment, this does catch bad solver arguments and paths, but also things like a broken pipe if a solver crashes, or exceptions thrown by internal code that relates to solver interaction (like failing to parse a solver result).

Copy link
Collaborator

@keyboardDrummer keyboardDrummer Jan 26, 2022

Choose a reason for hiding this comment

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

Inspecting the internals of AggregateException and rethrowing if it doesn't contain something we want to handle, seems fine to me.

This code will always lead to a message and an inconclusive result, so I wouldn't expect it to hide bugs.

That's true but I do think it is nicer to handle IO and input errors differently than bugs. As a user I'd also appreciate bugs being identified as such so I know immediately that I'm not to blame.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That sounds good to me. I'll update it to look inside the AggregateException.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

One possibility is to only handle IOException here. Solver configuration issues result in a ProverException which is then handled higher up, meaning the output expected by the current solver configuration error tests doesn't change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The latest commit does just this, and I think I like the way it behaves now (as detailed in the response below).

@@ -1244,6 +1244,13 @@ private static void CleanupCheckers(string requestId)
verificationResult.Errors = null;
verificationResult.Outcome = VCGen.Outcome.Inconclusive;
}
catch(AggregateException e)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we catch something more specific? I wouldn't want to swallow any bugs here but catching a bad solver argument seems good.

printer.AdvisoryWriteLine("Advisory: {0} SKIPPED because: {1}",
impl.Name, e.Message);
verificationResult.Errors = null;
verificationResult.Outcome = VCGen.Outcome.Inconclusive;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we set the default of verificationResult.Outcome to Inconclusive? Right now the default is Outcome.Correct, which seems like asking for soundness issues.

printer.AdvisoryWriteLine("Advisory: {0} SKIPPED because: {1}",
impl.Name, e.Message);
verificationResult.Errors = null;
verificationResult.Outcome = VCGen.Outcome.Inconclusive;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Inconclusive means that the solver ran but couldn't prove unsat or sat right? For the scenario of invalid solver arguments I wouldn't mind an additional enum value, like InvalidConfiguration.

@@ -105,6 +105,7 @@ async Task DoWork(Split split)
}
catch {
split.ReleaseChecker();
throw; // This can be better handled farther up the call stack.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nitpick: I don't think the comment is necessary.

@@ -1784,6 +1818,10 @@ private static ErrorInformation CreateErrorInformation(Counterexample error, VC.
{
cause = "Out of memory on";
}
else if (outcome == VCGen.Outcome.SolverException)
{
cause = "Solver exception on";
Copy link
Collaborator

Choose a reason for hiding this comment

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

From a user perspective, I think I like to know "is it my fault?" and "solver exception" seems a bit ambiguous is that regard. It sounds like it's the solver's fault, but this problem also occurs when you give incorrect solver arguments right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's right. It'll be hard to distinguish between an invalid solver and a crashing solver without digging into the text of the error message accompanying the exception, though. I think both will be instances of IOException. That text will be presented to the user, though, so they'll be able to tell the difference, even though it'll be toward the end of the message rather than the beginning.

Copy link
Collaborator Author

@atomb atomb Jan 26, 2022

Choose a reason for hiding this comment

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

With the most recent change, this is better behaved. An invalid solver path or option will lead to the old behavior: quitting immediately with a fatal error. A solver that starts up and then crashes will lead to the new behavior: a warning and a result of "solver exception", allowing the user to still get results for any proof obligation on which the solver succeeds.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks great, thanks :-)

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.

2 participants