mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[vm] Add @pragma('vm:keep-name') annotation
This allows us to keep symbol names for classes/methods/fields - even if obfuscation is enabled, but has no other effect on tree shaker / ... For go/dart-ama TEST=vm/dart/keep_name_pragma_test Change-Id: I66c0fc32217d9180f821658bae463f2c1d7fb1af Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/309740 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Auto-Submit: Martin Kustermann <kustermann@google.com>
This commit is contained in:
parent
788a3a95c2
commit
ddc236ba64
|
@ -18,6 +18,7 @@ import 'package:smith/smith.dart';
|
|||
final Configuration _configuration = Configuration.parse(
|
||||
const String.fromEnvironment("test_runner.configuration"),
|
||||
<String, dynamic>{});
|
||||
String configAsString = _configuration.toString();
|
||||
|
||||
bool get isDart2jsConfiguration => _configuration.compiler == Compiler.dart2js;
|
||||
|
||||
|
|
|
@ -27,11 +27,11 @@ class ObfuscationProhibitionsVisitor extends RecursiveVisitor {
|
|||
|
||||
ObfuscationProhibitionsVisitor(this.parser);
|
||||
|
||||
void _addIfEntryPoint(
|
||||
void _checkAnnotations(
|
||||
List<Expression> annotations, String name, TreeNode node) {
|
||||
for (var ann in annotations) {
|
||||
ParsedPragma? pragma = parser.parsePragma(ann);
|
||||
if (pragma is ParsedEntryPointPragma) {
|
||||
for (final annotation in annotations) {
|
||||
final pragma = parser.parsePragma(annotation);
|
||||
if (pragma is ParsedEntryPointPragma || pragma is ParsedKeepNamePragma) {
|
||||
metadata.protectedNames.add(name);
|
||||
if (node is Field) {
|
||||
metadata.protectedNames.add(name + "=");
|
||||
|
@ -54,22 +54,22 @@ class ObfuscationProhibitionsVisitor extends RecursiveVisitor {
|
|||
|
||||
@override
|
||||
visitClass(Class klass) {
|
||||
_addIfEntryPoint(klass.annotations, klass.name, klass);
|
||||
_checkAnnotations(klass.annotations, klass.name, klass);
|
||||
klass.visitChildren(this);
|
||||
}
|
||||
|
||||
@override
|
||||
visitConstructor(Constructor ctor) {
|
||||
_addIfEntryPoint(ctor.annotations, ctor.name.text, ctor);
|
||||
_checkAnnotations(ctor.annotations, ctor.name.text, ctor);
|
||||
}
|
||||
|
||||
@override
|
||||
visitProcedure(Procedure proc) {
|
||||
_addIfEntryPoint(proc.annotations, proc.name.text, proc);
|
||||
_checkAnnotations(proc.annotations, proc.name.text, proc);
|
||||
}
|
||||
|
||||
@override
|
||||
visitField(Field field) {
|
||||
_addIfEntryPoint(field.annotations, field.name.text, field);
|
||||
_checkAnnotations(field.annotations, field.name.text, field);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const kResultTypeUsesPassedTypeArguments =
|
|||
"result-type-uses-passed-type-arguments";
|
||||
const kVmRecognizedPragmaName = "vm:recognized";
|
||||
const kVmDisableUnboxedParametersPragmaName = "vm:disable-unboxed-parameters";
|
||||
const kVmKeepNamePragmaName = "vm:keep-name";
|
||||
|
||||
// Pragmas recognized by dart2wasm
|
||||
const kWasmEntryPointPragmaName = "wasm:entry-point";
|
||||
|
@ -57,6 +58,10 @@ class ParsedDisableUnboxedParameters extends ParsedPragma {
|
|||
const ParsedDisableUnboxedParameters();
|
||||
}
|
||||
|
||||
class ParsedKeepNamePragma extends ParsedPragma {
|
||||
const ParsedKeepNamePragma();
|
||||
}
|
||||
|
||||
abstract class PragmaAnnotationParser {
|
||||
/// May return 'null' if the annotation does not represent a recognized
|
||||
/// @pragma.
|
||||
|
@ -159,6 +164,8 @@ class ConstantPragmaAnnotationParser extends PragmaAnnotationParser {
|
|||
return ParsedRecognized(type);
|
||||
case kVmDisableUnboxedParametersPragmaName:
|
||||
return const ParsedDisableUnboxedParameters();
|
||||
case kVmKeepNamePragmaName:
|
||||
return ParsedKeepNamePragma();
|
||||
case kWasmEntryPointPragmaName:
|
||||
return ParsedEntryPointPragma(PragmaEntryPointType.Default);
|
||||
case kWasmExportPragmaName:
|
||||
|
|
|
@ -10,6 +10,7 @@ These pragmas are part of the VM's API and are safe for use in external code.
|
|||
| `vm:never-inline` | [Never inline a function or method](compiler/pragmas_recognized_by_compiler.md#requesting-a-function-never-be-inlined) |
|
||||
| `vm:prefer-inline` | [Inline a function or method when possible](compiler/pragmas_recognized_by_compiler.md#requesting-a-function-be-inlined) |
|
||||
| `vm:notify-debugger-on-exception` | Marks a function that catches exceptions, making the VM treat any caught exception as if they were uncaught. This can be used to notify an attached debugger during debugging, without pausing the app during regular execution. |
|
||||
| `vm:keep-name` | Will ensure we keep the name of the class/function - even if e.g. obfuscation mode is enabled. |
|
||||
| `vm:external-name` | Allows to specify an external (native) name for an `external` function. This name is used to lookup native implementation via native resolver associated with the current library through embedding APIs. This is a replacement for legacy VM specific `native "name"` syntax. |
|
||||
| `vm:invisible` | Allows to mark a function as invisible so it will not appear on stack traces. |
|
||||
| `vm:always-consider-inlining` | Marks a function which particularly benefits from inlining and specialization in context of the caller (for example, when concrete types of arguments are known). Inliner will not give up after one failed inlining attempt and will continue trying to inline this function. |
|
||||
|
|
111
runtime/tests/vm/dart/keep_name_pragma_test.dart
Normal file
111
runtime/tests/vm/dart/keep_name_pragma_test.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// VMOptions=--resolve-dwarf-paths --save-debugging-info=$TEST_COMPILATION_DIR/debug.so
|
||||
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:native_stack_traces/native_stack_traces.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
final dwarfPath =
|
||||
path.join(Platform.environment['TEST_COMPILATION_DIR']!, 'debug.so');
|
||||
final usesObfuscation =
|
||||
const String.fromEnvironment("test_runner.configuration")
|
||||
.contains('obfuscate');
|
||||
final usesDwarf =
|
||||
const String.fromEnvironment("test_runner.configuration").contains('dwarf');
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
if (Platform.isAndroid) return;
|
||||
|
||||
final List<String> stack = await run(6);
|
||||
final o = usesObfuscation ? '!' : '';
|
||||
|
||||
compareFrames([
|
||||
'${o}bottom',
|
||||
'KeepClass.keepMethod',
|
||||
'${o}NormalClass.normalMethod',
|
||||
'keepStatic',
|
||||
'${o}normalStatic',
|
||||
'${o}run',
|
||||
], stack);
|
||||
}
|
||||
|
||||
void compareFrames(List<String> patterns, List<String> stack) {
|
||||
if (patterns.length != stack.length) {
|
||||
throw 'Expected ${patterns.length} frames';
|
||||
}
|
||||
print('Comparing this pattern: \n ${patterns.join('\n ')}');
|
||||
print('Against these frames: \n ${stack.join('\n ')}');
|
||||
for (int i = 0; i < patterns.length; ++i) {
|
||||
final pattern = patterns[i];
|
||||
final frame = stack[i];
|
||||
if (pattern.startsWith('!')) {
|
||||
Expect.notEquals(pattern.substring(1), frame);
|
||||
} else {
|
||||
Expect.equals(pattern, frame);
|
||||
}
|
||||
}
|
||||
print('');
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
void bottom() {
|
||||
throw 'bad';
|
||||
}
|
||||
|
||||
@pragma('vm:keep-name')
|
||||
class KeepClass {
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:keep-name')
|
||||
void keepMethod() {
|
||||
bottom();
|
||||
}
|
||||
}
|
||||
|
||||
class NormalClass {
|
||||
@pragma('vm:never-inline')
|
||||
void normalMethod() {
|
||||
keep.keepMethod();
|
||||
}
|
||||
}
|
||||
|
||||
final normal = NormalClass();
|
||||
final keep = KeepClass();
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:keep-name')
|
||||
void keepStatic() {
|
||||
normal.normalMethod();
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
void normalStatic() {
|
||||
keepStatic();
|
||||
}
|
||||
|
||||
Future<List<String>> run(int n) async {
|
||||
try {
|
||||
normalStatic();
|
||||
} catch (e, s) {
|
||||
List<String> lines = s.toString().split('\n');
|
||||
if (usesDwarf) {
|
||||
final dwarf = Dwarf.fromFile(dwarfPath)!;
|
||||
lines = await Stream<String>.fromIterable(lines)
|
||||
.transform(DwarfStackTraceDecoder(dwarf))
|
||||
.toList();
|
||||
}
|
||||
final start = lines.indexWhere((line) => line.startsWith('#0'));
|
||||
lines = lines.skip(start).take(n).toList();
|
||||
return lines.map((String line) {
|
||||
line = line.substring(line.indexOf(' ')).trim();
|
||||
line = line.substring(0, line.indexOf(' ')).trim();
|
||||
return line;
|
||||
}).toList();
|
||||
}
|
||||
throw 'failed';
|
||||
}
|
Loading…
Reference in a new issue