Skip to content

Flow analysis can be used to escalate mixed mode unsoundness #1143

@stereotype441

Description

@stereotype441

For a very long time, I thought that the weakening of soundness in mixed mode was limited to nullability. That is, I thought that in mixed mode:

  • within an opted out library, the result of evaluating an expression having static type T would always be a value whose runtime type is a subtype of T (because T contains the *s necessary to permit nulls in the appropriate places)
  • within an opted in library, the result of evaluating an expression having static type T would always be a value whose runtime type is a subtype of T', where T' is formed from T by adding/removing *s to T in appropriate positions.

But that turns out not to be the case. The following program breaks soundness by allowing a String to be assigned to a variable of type int. Both the analyzer and CFE accept it without issuing any errors:

test.dart (opted out):

// @dart=2.6

import 'opted_in.dart';

T violateSoundness<T>(dynamic x) => accomplice<T>(x, null);

main() {
  String s = 'soudness, my old nemesis, we meet again!';
  int i = violateSoundness<int>(s);
  print(i.abs());
}

opted_in.dart:

// @dart=2.10

T accomplice<T>(Object? x, int i) {
  if (x is! T) {
    if (i is int) throw 0;
  }
  return x;
}

What's happening is that flow analysis considers the implicit "else" branch of if (i is int) to promote the type of i to Never, hence it considers it unreachable. So it considers the flow state after if (i is int) throw 0; to be unreachable, and hence it considers if (x is! T) { if (i is int) throw 0; } to promote the type of x to T. So it believes that the return x; at the end of accomplice is sound.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions