-
Notifications
You must be signed in to change notification settings - Fork 116
TPE: Simplify <residual> && false to false when <residual> cannot error
#2091
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
base: main
Are you sure you want to change the base?
TPE: Simplify <residual> && false to false when <residual> cannot error
#2091
Conversation
…e` to `true` when `<residual>` cannot error.
|
|
||
| ResidualKind::BinaryApp { op, arg1, arg2 } => match op { | ||
| // Arithmetic operations could error due to integer overflow | ||
| ast::BinaryOp::Add => true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there other cases than these, or gaps in the validator coverage that I'd need to know about?
Coverage ReportHead Commit: Base Commit: Download the full coverage report. Coverage of Added or Modified Lines of Rust CodeRequired coverage: 80.00% Actual coverage: 98.46% Status: PASSED ✅ Details
Coverage of All Lines of Rust CodeRequired coverage: 80.00% Actual coverage: 86.15% Status: PASSED ✅ Details
|
Coverage ReportHead Commit: Base Commit: Download the full coverage report. Coverage of Added or Modified Lines of Rust CodeRequired coverage: 80.00% Actual coverage: 98.46% Status: PASSED ✅ Details
Coverage of All Lines of Rust CodeRequired coverage: 80.00% Actual coverage: 86.15% Status: PASSED ✅ Details
|
| // Extension function invocations can error at runtime. | ||
| ResidualKind::ExtensionFunctionApp { .. } => true, | ||
|
|
||
| ResidualKind::GetAttr { expr, .. } => expr.can_error_assuming_well_formed(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and getTag are the other important cases. They will error if the target entity isn't in the entity data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These also gets at some tricky cases around optional attributes. The type annotated AST doesn't carry the capability information used to check that optional attributes will be present, so just having a locally well typed resdiual isn't enough to guarantee that there will never be an unknown attribute error. (the residuals resulting from typechecking will satisfy this property, but it's trickier to show that is holds for the residual resulting from partial eval).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is why I was suggesting that the only way to construct a Residual (even in cedar-policy-core) would be from an Expr that has been validated. For such a Residual, the validator made sure through its capabilities that any getAttr or getTag present (in any sub-expr of the root) cannot error, right? Even (and especially) if the attribute is optional.
This is why I am suggesting to narrow down how Residuals can be constructed (even in cedar-policy-core), so that we can uphold the invariant that getAttr or getTag can never error, as any Residual instance had to necessarily pass validation even before becoming constructed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optional attribute point is just an aside. It doesn't impact correctness, just makes it a pain to prove.
The real issue is that getTag and getAttr will both error whenever the entity they access doesn't exist. This is slightly subtle for TPE since the missing entity is initially treated as an unknown value (resulting in a non-concrete residual rather than an error), but when we go to concretely evaluate the residual, it's still an error if the data for the entity isn't present.
Validation doesn't do anything to guard against this sort of error. It's just one of the gaps in it's soundness theorem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, you were thinking about errors from concrete reauthorization too; I was only thinking about whether TPE itself could produce errors, which getTag and getAttr would not (a missing entity would just yield another residual). Good catch. That limits the usefulness of this rewrite quite a bit, hmm...
To some extent, that type of error "feels different" though, in that the schema "promised" that all entity references could be followed, but the implementation did not live up to that promise. Maybe one way to find a middle-ground here would be to let the implementation (the TPE Evaluator) provide (or not) explicitly whether it guarantees that all referenced entities exist, or if it cannot guarantee this.
However, this PR (even if I add that these operators can error too) gets us some of the way, and most likely the possible_bool_outcomes described in #1895 (comment) gets us the rest of the way for those users that do not care about the error precision between false and error for permit policies, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that getAttr can error definitely limits the usefulness of this optimization since it folding away an attribute access to eliminate the need to fetch that entity data is exactly what I'd ideally want it to do.
I guess this just points us towards looking at the PossibleBoolOutcome idea or changing the auth algorithm to account for the possibility of errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To some extent, that type of error "feels different" though, in that the schema "promised" that all entity references could be followed, but the implementation did not live up to that promise. Maybe one way to find a middle-ground here would be to let the implementation (the TPE Evaluator) provide (or not) explicitly whether it guarantees that all referenced entities exist, or if it cannot guarantee this.
There's a possibility of something like this, probably by using level-based policy validation and entity slicing as a pre-condition, but I don't think we'll want to make this change too quickly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
folding away an attribute access to eliminate the need to fetch that entity data is exactly what I'd ideally want it to do
Yes, this exactly 👍
There's a possibility of something like this, probably by using level-based policy validation and entity slicing as a pre-condition, but I don't think we'll want to make this change too quickly.
Cool, let's discuss this separately then in #1895 or a new after we've merged this initial optimization.
I'll make the change for getAttr and getTag next. Should I also rename the From<Expr<Option<Type>>> for Residual "constructor" and make Residual(Kind) private (as per option a above), and instead add the constructor tpe::make_residual(e: Expr, req: &PartialRequest, s: &ValidatorSchema) -> Result<Residual, TpeError> that makes sure Evaluator::interpret is always sound? (If not, the interpret function would be unsound on Residuals that are not validated)
We're fine with (c) so long as you're talking about the behavior when calling In other words, our point of view is that we're doing (a) (keeping the constructor private) as long as it's not exported from |
|
for other reviewers: a partial Lean proof of this simplification is in this branch https://github.com/cedar-policy/cedar-spec/tree/tpe_and_false The proof is complete (no |
| pub fn can_error_assuming_well_formed(&self) -> bool { | ||
| match self { | ||
| Residual::Concrete { .. } => false, | ||
| Residual::Error(_) => true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like this line isn't covered by any tests. Seems worth writing one
See my comment #2091 (comment); I was thinking to make the |
Description of changes
Adds a method that determines if a residual can produce runtime errors that the validator would not already warn the user about (hence the "well-typed" assumption). See #1895 for further discussion. The function could be made slightly more precise by indicating what extension functions can and cannot error.
The "can error" method is used to simplify
<residual> && falsetofalseand<residual> || truetotruewhen<residual>cannot error. Simplification cannot be done when the residual could error.Reviewer Notes
Note that there are lots of typing errors that can be thrown by
Evaluator.interpreton arbitrarily constructedResiduals. So do you prefera) Making
Residualconstruction private, so it can only be constructed by the typechecker after a successful pass, orb) Somehow trying to inline what the validator would do in
Evaluator.interpretevery time we need to know if a sub-expr is well-formed or not (complex and seems slow), orc) Keep the behavior undefined for anyone that constructs semantically invalid
Residuals (seems dangerous)I guess I lean towards a), at least until we know if anyone needs those enums to be public.
In addition, I think it'd be a good idea to use separate
Residual::Errorin toResidual::TypeErrorandResidual::RuntimeError, where Residual::TypeErrorshould never happen (if validator is sound), butResidual::RuntimeErrorcould indeed. This might be a good time to add the same context toResidual::RuntimeError` as for normal evaluation (#1736).Issue #, if available
Fixes: #1895
(or at least gets us some way towards that goal)
Checklist for requesting a review
The change in this PR is
I confirm that this PR
I confirm that
cedar-spec(choose one, and delete the other options):I confirm that
docs.cedarpolicy.com(choose one, and delete the other options):