Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0e5a539

Browse files
alexmarkovCommit Bot
authored andcommitted
[vm/aot] Keep toString methods on exception classes in toString transformer
Flutter in google3 uses --delete-tostring-package-uri compiler option to remove toString methods in package:flutter and dart:ui to reduce size in release mode. This has unfortunate effect of removing toString methods from exception classes which may provide valuable information for investigating problems seen in the wild. This change adds a new @pragma('flutter:keep-to-string-in-subtypes') on classes to keep toString methods on all subtypes of the annotated classes. This pragma is now used on Exception and Error classes in dart:core. TEST=pkg/vm/test/transformations/to_string_transformer_test.dart Issue: flutter/flutter#61562 Change-Id: Ib739c83cdf6b539208f705ba198e63b8bc54fa61 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/227920 Reviewed-by: Dan Field <[email protected]> Reviewed-by: Slava Egorov <[email protected]> Commit-Queue: Alexander Markov <[email protected]>
1 parent 442836b commit 0e5a539

File tree

7 files changed

+134
-4
lines changed

7 files changed

+134
-4
lines changed

pkg/vm/lib/transformations/to_string_transformer.dart

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class ToStringVisitor extends RecursiveVisitor {
1919
/// 'package:flutter/foundation.dart'.
2020
final Set<String> _packageUris;
2121

22+
final Map<Class, bool> _inheritedKeepAnnotations = {};
23+
2224
/// Turn 'dart:ui' into 'dart:ui', or
2325
/// 'package:flutter/src/semantics_event.dart' into 'package:flutter'.
2426
String _importUriToPackage(Uri importUri) =>
@@ -29,7 +31,18 @@ class ToStringVisitor extends RecursiveVisitor {
2931
.contains(_importUriToPackage(node.enclosingLibrary.importUri));
3032
}
3133

32-
bool _hasKeepAnnotation(Procedure node) {
34+
bool _hasKeepAnnotation(Procedure node) =>
35+
_hasPragma(node, 'flutter:keep-to-string');
36+
37+
bool _hasKeepAnnotationOnClass(Class node) =>
38+
_hasPragma(node, 'flutter:keep-to-string-in-subtypes');
39+
40+
bool _hasInheritedKeepAnnotation(Class node) =>
41+
_inheritedKeepAnnotations[node] ??= (_hasKeepAnnotationOnClass(node) ||
42+
node.supers
43+
.any((Supertype t) => _hasInheritedKeepAnnotation(t.classNode)));
44+
45+
bool _hasPragma(Annotatable node, String pragma) {
3346
for (ConstantExpression expression
3447
in node.annotations.whereType<ConstantExpression>()) {
3548
if (expression.constant is! InstanceConstant) {
@@ -43,8 +56,7 @@ class ToStringVisitor extends RecursiveVisitor {
4356
for (var fieldRef in constant.fieldValues.keys) {
4457
if (fieldRef.asField.name.text == 'name') {
4558
Constant? name = constant.fieldValues[fieldRef];
46-
return name is StringConstant &&
47-
name.value == 'flutter:keep-to-string';
59+
return name is StringConstant && name.value == pragma;
4860
}
4961
}
5062
return false;
@@ -61,7 +73,8 @@ class ToStringVisitor extends RecursiveVisitor {
6173
!node.isAbstract &&
6274
!node.enclosingClass!.isEnum &&
6375
_isInTargetPackage(node) &&
64-
!_hasKeepAnnotation(node)) {
76+
!_hasKeepAnnotation(node) &&
77+
!_hasInheritedKeepAnnotation(node.enclosingClass!)) {
6578
node.function.body!.replaceWith(
6679
ReturnStatement(
6780
SuperMethodInvocation(

pkg/vm/testcases/transformations/to_string_transformer/lib/main.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:convert';
66

77
const keepToString = pragma('flutter:keep-to-string');
8+
const keepToStringInSubtypes = pragma('flutter:keep-to-string-in-subtypes');
89

910
String toString() => 'I am static';
1011

@@ -26,9 +27,28 @@ class Keep {
2627
String toString() => 'I am a Keep';
2728
}
2829

30+
@keepToStringInSubtypes
31+
class Base1 {}
32+
33+
class Base2 extends Base1 {}
34+
35+
class Base3 extends Object with Base2 {}
36+
37+
class KeepInherited implements Base3 {
38+
@override
39+
String toString() => 'Heir';
40+
}
41+
42+
class MyException implements Exception {
43+
@override
44+
String toString() => 'A very detailed message';
45+
}
46+
2947
void main() {
3048
final IFoo foo = Foo();
3149
print(foo.toString());
3250
print(Keep().toString());
3351
print(FooEnum.B.toString());
52+
print(KeepInherited().toString());
53+
print(MyException().toString());
3454
}

pkg/vm/testcases/transformations/to_string_transformer/not_transformed.expect

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,54 @@ class Keep extends core::Object {
3939
method toString() → core::String
4040
return "I am a Keep";
4141
}
42+
@#C16
43+
class Base1 extends core::Object {
44+
synthetic constructor •() → self::Base1
45+
: super core::Object::•()
46+
;
47+
}
48+
class Base2 extends self::Base1 {
49+
synthetic constructor •() → self::Base2
50+
: super self::Base1::•()
51+
;
52+
}
53+
abstract class _Base3&Object&Base2 extends core::Object implements self::Base2 /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ {
54+
const synthetic constructor •() → self::_Base3&Object&Base2
55+
: super core::Object::•()
56+
;
57+
}
58+
class Base3 extends self::_Base3&Object&Base2 {
59+
synthetic constructor •() → self::Base3
60+
: super self::_Base3&Object&Base2::•()
61+
;
62+
}
63+
class KeepInherited extends core::Object implements self::Base3 {
64+
synthetic constructor •() → self::KeepInherited
65+
: super core::Object::•()
66+
;
67+
@#C1
68+
method toString() → core::String
69+
return "Heir";
70+
}
71+
class MyException extends core::Object implements core::Exception {
72+
synthetic constructor •() → self::MyException
73+
: super core::Object::•()
74+
;
75+
@#C1
76+
method toString() → core::String
77+
return "A very detailed message";
78+
}
4279
static const field core::pragma keepToString = #C14;
80+
static const field core::pragma keepToStringInSubtypes = #C16;
4381
static method toString() → core::String
4482
return "I am static";
4583
static method main() → void {
4684
final self::IFoo foo = new self::Foo::•();
4785
core::print(foo.{self::IFoo::toString}(){() → core::String});
4886
core::print(new self::Keep::•().{self::Keep::toString}(){() → core::String});
4987
core::print(#C7.{self::FooEnum::toString}(){() → core::String});
88+
core::print(new self::KeepInherited::•().{self::KeepInherited::toString}(){() → core::String});
89+
core::print(new self::MyException::•().{self::MyException::toString}(){() → core::String});
5090
}
5191
constants {
5292
#C1 = core::_Override {}
@@ -63,4 +103,6 @@ constants {
63103
#C12 = "flutter:keep-to-string"
64104
#C13 = null
65105
#C14 = core::pragma {name:#C12, options:#C13}
106+
#C15 = "flutter:keep-to-string-in-subtypes"
107+
#C16 = core::pragma {name:#C15, options:#C13}
66108
}

pkg/vm/testcases/transformations/to_string_transformer/transformed.expect

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,54 @@ class Keep extends core::Object {
3939
method toString() → core::String
4040
return "I am a Keep";
4141
}
42+
@#C16
43+
class Base1 extends core::Object {
44+
synthetic constructor •() → self::Base1
45+
: super core::Object::•()
46+
;
47+
}
48+
class Base2 extends self::Base1 {
49+
synthetic constructor •() → self::Base2
50+
: super self::Base1::•()
51+
;
52+
}
53+
abstract class _Base3&Object&Base2 extends core::Object implements self::Base2 /*isAnonymousMixin,isEliminatedMixin,hasConstConstructor*/ {
54+
const synthetic constructor •() → self::_Base3&Object&Base2
55+
: super core::Object::•()
56+
;
57+
}
58+
class Base3 extends self::_Base3&Object&Base2 {
59+
synthetic constructor •() → self::Base3
60+
: super self::_Base3&Object&Base2::•()
61+
;
62+
}
63+
class KeepInherited extends core::Object implements self::Base3 {
64+
synthetic constructor •() → self::KeepInherited
65+
: super core::Object::•()
66+
;
67+
@#C1
68+
method toString() → core::String
69+
return "Heir";
70+
}
71+
class MyException extends core::Object implements core::Exception {
72+
synthetic constructor •() → self::MyException
73+
: super core::Object::•()
74+
;
75+
@#C1
76+
method toString() → core::String
77+
return "A very detailed message";
78+
}
4279
static const field core::pragma keepToString = #C14;
80+
static const field core::pragma keepToStringInSubtypes = #C16;
4381
static method toString() → core::String
4482
return "I am static";
4583
static method main() → void {
4684
final self::IFoo foo = new self::Foo::•();
4785
core::print(foo.{self::IFoo::toString}(){() → core::String});
4886
core::print(new self::Keep::•().{self::Keep::toString}(){() → core::String});
4987
core::print(#C7.{self::FooEnum::toString}(){() → core::String});
88+
core::print(new self::KeepInherited::•().{self::KeepInherited::toString}(){() → core::String});
89+
core::print(new self::MyException::•().{self::MyException::toString}(){() → core::String});
5090
}
5191
constants {
5292
#C1 = core::_Override {}
@@ -63,4 +103,6 @@ constants {
63103
#C12 = "flutter:keep-to-string"
64104
#C13 = null
65105
#C14 = core::pragma {name:#C12, options:#C13}
106+
#C15 = "flutter:keep-to-string-in-subtypes"
107+
#C16 = core::pragma {name:#C15, options:#C13}
66108
}

runtime/docs/pragmas.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,14 @@ Additionally, they are categorized into "safe" and "unsafe" forms: "safe" pragma
4141
| Pragma | Meaning |
4242
| --- | --- |
4343
| `vm:testing.unsafe.trace-entrypoints-fn` | [Observing which flow-graph-level entry-point was used when a function was called](compiler/frontend/testing_trace_entrypoints_pragma.md) |
44+
45+
## Flutter toString transformer pragmas
46+
47+
These pragmas are useful to exclude certain toString methods from toString transformation,
48+
which is enabled with `--delete-tostring-package-uri` option in kernel compilers and
49+
used by Flutter to remove certain toString methods in release mode to reduce size.
50+
51+
| Pragma | Meaning |
52+
| --- | --- |
53+
| `flutter:keep-to-string` | Avoid transforming the annotated toString method. |
54+
| `flutter:keep-to-string-in-subtypes` | Avoid transforming toString methods in all subtypes of the annotated class. |

sdk/lib/core/errors.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ part of dart.core;
6464
/// For example, the [String.contains] method will use a [RangeError]
6565
/// if its `startIndex` isn't in the range `0..length`,
6666
/// which is easily created by `RangeError.range(startIndex, 0, length)`.
67+
@pragma('flutter:keep-to-string-in-subtypes')
6768
class Error {
6869
Error(); // Prevent use as mixin.
6970

sdk/lib/core/exceptions.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ part of dart.core;
1616
/// is discouraged in library code since it doesn't give users a precise
1717
/// type they can catch. It may be reasonable to use instances of this
1818
/// class in tests or during development.
19+
@pragma('flutter:keep-to-string-in-subtypes')
1920
abstract class Exception {
2021
factory Exception([var message]) => _Exception(message);
2122
}

0 commit comments

Comments
 (0)