-
Notifications
You must be signed in to change notification settings - Fork 213
Unsoundness escalation with patterns #2996
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
Comments
Amusingly, the VM seems to be optimizing that code based on an assumption of completely sound types, so it actually enters the I believe we have specified for exhaustive switches that a default case is inserted to handle In general, unless we see this actually cropping up (unlikely) I don't think we should spend time fixing this. Unsound mode is no longer fully supported, and will be removed entirely as soon as the last bits of internal code have been ported or deleted. |
The CFE already almost does Option 2: In weak/agnostic mode it inserts a throw in all switch expressions and in switch statements with an always-exhaustive type without a default case. Nothing is currently done for if-case statements. |
Ah, so in effect the implementation does most of option 3. So I guess the question is whether we should cover the remaining soundness gaps (and add tests to make sure the functionality doesn't regress) or just leave things as they are. |
I believe the proposal already specifies option 3. See Pointless type tests and legacy types. That change was made to the spec because of this related issue: #2619. Personally, I'm fine with any blend of options 1 and 3. I don't think it makes sense to spend engineering time or increase code size because of a pair of features that almost no user will ever be able to combine. |
Aha, I'd forgotten about that! Thanks, Bob.
Sounds good. Based on the discussion in Wednesday's language team meeting, it sounds like we are all on board with accepting the current situation, and not worrying about it further, so I'll close this issue. |
Back in 2020, we put a bunch of effort into making sure that it the fundamentally unsound nature of programs running in weak mode is limited: in a weak mode program, an expression whose static type is the non-nullable type
T
might actually benull
(due to thenull
coming from a legacy library), but if it's notnull
, it will definitely be an instance of typeT
. Similarly, an expression whose static type is the potentially nullable typeT
might benull
even if the actual instatiated type ofT
at runtime is a non-nullable type. That is to say, the unsoundness aroundnull
s in weak mode can't be "escalated" to unsoundness involving non-null values. See #1143 for background.I've just realised that with the introduction of patterns, there are now new loopholes for unsoundness escalation that we haven't closed. For example:
Under the VM, this
unsafeCast
can be used to reinterpret any value of any type as a value of any other type. It's maximally unsound.This technique can be used to escalate unsoundness using any construct that causes flow analysis to think a code path is unreachable when it's actually reachable due to a value unexpectedly being
null
, e.g. the switch statement above could be replaced with an if-case statement:or an if-case collection element:
or a switch expression:
Ideas for what to do about this:
when true
always matches.else
codepath is considered unreachable).break
(and hence the code after the switch statement is considered unreachable).then
codepath are considered unreachable).null
, because there might be side effects on other variables that could be used for unsoundness escalation).null
value might still slip through, but only in a code path that flow analysis believes to be reachable, so there would be no opportunity for unsoundness escalation. The remaining bullets would still be addressed in the same way as in option 2.int
is guaranteed to match the patternint()
.Option 1 obviously has the disadvantage that it's unsound. But it has the advantage that it behaves in a way that's intuitive to users (who won't be thinking about this unsoundness escalation issue). And, as a bonus, it doesn't require any additional coding work (or cherry picks), because it's what we accidentally already did!
Option 2 probably gives the best user experience; it allows the user to assume full soundness, and see that assumption reflected in static analysis; there may be occasional runtime exceptions due to
null
s being injected by legacy libraries, but hopefully this won't be too surprising to users, since they know that this is a risk if they're running a mix of null-safe and legacy code. Unfortunately it's a nontrivial amount of work. However, it may bloat the size of the compiled executable due to all the synthetic throws.Option 3 also gives a good user experience. It's possibly slightly worse in that e.g.
int()
can now sometimes matchnull
. The advantage over option 2 is that it would bloat the code less. In fact, for fully sound programs, we could consider it an optimisation, since all the type tests that would be dropped are provably unnecessary in fully sound programs. Unfortunately it has similar implementation difficulty to option 2.Option 4 is kind a the nuclear option and I don't like it. It's nontrivial to implement and it would result in a substantially worse user experience, because flow analysis would start considering code paths to be reachable that the user doesn't intend to be reachable (and that the user likely knows to be unreachable due to non-local analysis). But it has the advantage that it could be implemented entirely inside flow analysis, without any corresponding changes to the CFE.
Personally I favor option 1, because I think it's very unlikely that the situation will ever arise for real-world users. And the loophole will automatically close when we drop support for legacy code. But since option 1 is unsound I don't want to do it without a consensus that it's a level of unsoundness we're willing to accept.
CC @dart-lang/language-team @johnniwinther
The text was updated successfully, but these errors were encountered: