b66bab009c
This CL ensures that the member environment for the enclosing class of a live member is computed. Static members and mixed in members a member can be become live through static access and mixin application, respectively, which does not require lookup into the class members. Since the J-model class environment is computed from the K-model environment, not ensuring the computation of the class members, can result in a live member being present in the J-model but unavailable when queried as a member of its enclosing class. Closes #44754 Change-Id: I5a735f4605737947cca082450803c35887b0b7a0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/181801 Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
468 lines
16 KiB
Dart
468 lines
16 KiB
Dart
// Copyright (c) 2019, 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.
|
|
|
|
// @dart = 2.7
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:async_helper/async_helper.dart';
|
|
import 'package:compiler/src/compiler.dart';
|
|
import 'package:compiler/src/elements/entities.dart';
|
|
import 'package:compiler/src/ir/annotations.dart';
|
|
import 'package:compiler/src/js_backend/native_data.dart';
|
|
import 'package:compiler/src/kernel/kernel_strategy.dart';
|
|
import 'package:compiler/src/kernel/element_map.dart';
|
|
import 'package:compiler/src/kernel/element_map_impl.dart';
|
|
import 'package:expect/expect.dart';
|
|
import 'package:kernel/ast.dart' as ir;
|
|
|
|
import '../helpers/args_helper.dart';
|
|
import '../helpers/memory_compiler.dart';
|
|
|
|
const String pathPrefix = 'sdk/tests/web_2/native/';
|
|
|
|
const Map<String, String> source = {
|
|
'$pathPrefix/main.dart': '''
|
|
|
|
library lib;
|
|
|
|
import 'package:meta/dart2js.dart';
|
|
|
|
import 'jslib1.dart';
|
|
import 'jslib2.dart';
|
|
import 'nativelib.dart';
|
|
|
|
@pragma('dart2js:noInline')
|
|
method1() {}
|
|
|
|
@noInline
|
|
method2() {}
|
|
|
|
@pragma(const String.fromEnvironment('foo', defaultValue: 'dart2js:tryInline'))
|
|
method3() {}
|
|
|
|
@tryInline
|
|
method4() {}
|
|
|
|
main() {
|
|
method1();
|
|
method2();
|
|
method3();
|
|
method4();
|
|
new JsClass1()..jsMethod1()..jsMethod2();
|
|
new JsClass2();
|
|
jsMethod3();
|
|
new NativeClass1()..nativeMethod()..nativeField;
|
|
new NativeClass2()..nativeField;
|
|
new NativeClass3()..nativeMethod()..nativeGetter;
|
|
nativeMethod();
|
|
}
|
|
''',
|
|
'$pathPrefix/jslib1.dart': '''
|
|
|
|
@JS('lib1')
|
|
library lib1;
|
|
|
|
import 'package:js/js.dart';
|
|
|
|
@JS('JsInteropClass1')
|
|
class JsClass1 {
|
|
@JS('jsInteropMethod1')
|
|
external jsMethod1();
|
|
|
|
external jsMethod2();
|
|
}
|
|
|
|
''',
|
|
'$pathPrefix/jslib2.dart': '''
|
|
|
|
@JS()
|
|
library lib2;
|
|
|
|
import 'package:js/js.dart';
|
|
|
|
@JS()
|
|
@anonymous
|
|
class JsClass2 {
|
|
}
|
|
|
|
@JS('jsInteropMethod3')
|
|
external jsMethod3();
|
|
|
|
external jsMethod4();
|
|
''',
|
|
'$pathPrefix/nativelib.dart': '''
|
|
library lib3;
|
|
|
|
import 'dart:_js_helper';
|
|
|
|
@Native('Class1')
|
|
class NativeClass1 {
|
|
@JSName('field1')
|
|
var nativeField;
|
|
|
|
@JSName('method1')
|
|
nativeMethod() native;
|
|
}
|
|
|
|
@Native('Class2,!nonleaf')
|
|
class NativeClass2 {
|
|
@JSName('field2')
|
|
var nativeField;
|
|
}
|
|
|
|
@Native('Class3a,Class3b')
|
|
class NativeClass3 {
|
|
|
|
@JSName('method2')
|
|
get nativeGetter native;
|
|
|
|
@Creates('String')
|
|
@Returns('int')
|
|
nativeMethod() native;
|
|
}
|
|
|
|
@JSName('method3')
|
|
nativeMethod() native;
|
|
''',
|
|
};
|
|
|
|
const Map<String, String> expectedNativeClassNames = {
|
|
'$pathPrefix/nativelib.dart::NativeClass1': 'Class1',
|
|
'$pathPrefix/nativelib.dart::NativeClass2': 'Class2,!nonleaf',
|
|
'$pathPrefix/nativelib.dart::NativeClass3': 'Class3a,Class3b',
|
|
};
|
|
|
|
const Map<String, String> expectedNativeMemberNames = {
|
|
'$pathPrefix/nativelib.dart::NativeClass1::nativeField': 'field1',
|
|
'$pathPrefix/nativelib.dart::NativeClass1::nativeMethod': 'method1',
|
|
'$pathPrefix/nativelib.dart::NativeClass2::nativeField': 'field2',
|
|
'$pathPrefix/nativelib.dart::NativeClass3::nativeGetter': 'method2',
|
|
'$pathPrefix/nativelib.dart::nativeMethod': 'method3',
|
|
};
|
|
|
|
const Map<String, String> expectedCreates = {
|
|
'$pathPrefix/nativelib.dart::NativeClass3::nativeMethod': 'String',
|
|
};
|
|
|
|
const Map<String, String> expectedReturns = {
|
|
'$pathPrefix/nativelib.dart::NativeClass3::nativeMethod': 'int',
|
|
};
|
|
|
|
const Map<String, String> expectedJsInteropLibraryNames = {
|
|
'$pathPrefix/jslib1.dart': 'lib1',
|
|
'$pathPrefix/jslib2.dart': '',
|
|
};
|
|
|
|
const Map<String, String> expectedJsInteropClassNames = {
|
|
'$pathPrefix/jslib1.dart::JsClass1': 'JsInteropClass1',
|
|
'$pathPrefix/jslib2.dart::JsClass2': '',
|
|
};
|
|
|
|
const Map<String, String> expectedJsInteropMemberNames = {
|
|
'$pathPrefix/jslib1.dart::JsClass1::jsMethod1': 'jsInteropMethod1',
|
|
'$pathPrefix/jslib2.dart::jsMethod3': 'jsInteropMethod3',
|
|
};
|
|
|
|
const Set<String> expectedAnonymousJsInteropClasses = {
|
|
'$pathPrefix/jslib2.dart::JsClass2',
|
|
};
|
|
|
|
const Set<String> expectedNoInlineMethods = {
|
|
'$pathPrefix/main.dart::method1',
|
|
'$pathPrefix/main.dart::method2',
|
|
};
|
|
|
|
const Set<String> expectedTryInlineMethods = {
|
|
'$pathPrefix/main.dart::method3',
|
|
'$pathPrefix/main.dart::method4',
|
|
};
|
|
|
|
main(List<String> args) {
|
|
ArgParser argParser = createArgParser();
|
|
|
|
asyncTest(() async {
|
|
ArgResults argResults = argParser.parse(args);
|
|
Uri librariesSpecificationUri = getLibrariesSpec(argResults);
|
|
Uri packageConfig = getPackages(argResults);
|
|
List<String> options = getOptions(argResults);
|
|
|
|
runTest({bool useIr}) async {
|
|
CompilationResult result = await runCompiler(
|
|
entryPoint: Uri.parse('memory:$pathPrefix/main.dart'),
|
|
memorySourceFiles: source,
|
|
packageConfig: packageConfig,
|
|
librariesSpecificationUri: librariesSpecificationUri,
|
|
options: options);
|
|
Expect.isTrue(result.isSuccess);
|
|
Compiler compiler = result.compiler;
|
|
KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
|
|
KernelToElementMapImpl elementMap = frontendStrategy.elementMap;
|
|
ir.Component component = elementMap.env.mainComponent;
|
|
IrAnnotationData annotationData =
|
|
frontendStrategy.irAnnotationDataForTesting;
|
|
|
|
void testAll(NativeData nativeData) {
|
|
void testMember(String idPrefix, ir.Member member,
|
|
{bool implicitJsInteropMember, bool implicitNativeMember}) {
|
|
if (memberIsIgnorable(member)) return;
|
|
String memberId = '$idPrefix::${member.name.text}';
|
|
MemberEntity memberEntity = elementMap.getMember(member);
|
|
|
|
String expectedJsInteropMemberName =
|
|
expectedJsInteropMemberNames[memberId];
|
|
String expectedNativeMemberName = expectedNativeMemberNames[memberId];
|
|
Set<String> expectedPragmaNames = {};
|
|
if (expectedNoInlineMethods.contains(memberId)) {
|
|
expectedPragmaNames.add('dart2js:noInline');
|
|
}
|
|
if (expectedTryInlineMethods.contains(memberId)) {
|
|
expectedPragmaNames.add('dart2js:tryInline');
|
|
}
|
|
|
|
String expectedCreatesText = expectedCreates[memberId];
|
|
String expectedReturnsText = expectedReturns[memberId];
|
|
|
|
if (useIr) {
|
|
Expect.equals(
|
|
expectedJsInteropMemberName,
|
|
annotationData.getJsInteropMemberName(member),
|
|
"Unexpected js interop member name from IR for $member, "
|
|
"id: $memberId");
|
|
|
|
Expect.equals(
|
|
expectedNativeMemberName,
|
|
annotationData.getNativeMemberName(member),
|
|
"Unexpected js interop member name from IR for $member, "
|
|
"id: $memberId");
|
|
|
|
List<PragmaAnnotationData> pragmaAnnotations =
|
|
annotationData.getMemberPragmaAnnotationData(member);
|
|
Set<String> pragmaNames =
|
|
pragmaAnnotations.map((d) => d.name).toSet();
|
|
Expect.setEquals(expectedPragmaNames, pragmaNames,
|
|
"Unexpected pragmas from IR for $member, " "id: $memberId");
|
|
|
|
List<String> createsAnnotations =
|
|
annotationData.getCreatesAnnotations(member);
|
|
Expect.equals(
|
|
expectedCreatesText,
|
|
createsAnnotations.isEmpty
|
|
? null
|
|
: createsAnnotations.join(','),
|
|
"Unexpected create annotations from IR for $member, "
|
|
"id: $memberId");
|
|
|
|
List<String> returnsAnnotations =
|
|
annotationData.getReturnsAnnotations(member);
|
|
Expect.equals(
|
|
expectedReturnsText,
|
|
returnsAnnotations.isEmpty
|
|
? null
|
|
: returnsAnnotations.join(','),
|
|
"Unexpected returns annotations from IR for $member, "
|
|
"id: $memberId");
|
|
}
|
|
|
|
bool isJsInteropMember =
|
|
(implicitJsInteropMember && member.isExternal) ||
|
|
expectedJsInteropMemberName != null;
|
|
Expect.equals(
|
|
isJsInteropMember,
|
|
nativeData.isJsInteropMember(memberEntity),
|
|
"Unexpected js interop member result from native data for $member, "
|
|
"id: $memberId");
|
|
Expect.equals(
|
|
isJsInteropMember
|
|
? expectedJsInteropMemberName ?? memberEntity.name
|
|
: null,
|
|
nativeData.getJsInteropMemberName(memberEntity),
|
|
"Unexpected js interop member name from native data for $member, "
|
|
"id: $memberId");
|
|
|
|
bool isNativeMember =
|
|
implicitNativeMember || expectedNativeMemberName != null;
|
|
Expect.equals(
|
|
isNativeMember || isJsInteropMember,
|
|
nativeData.isNativeMember(memberEntity),
|
|
"Unexpected native member result from native data for $member, "
|
|
"id: $memberId");
|
|
Expect.equals(
|
|
isNativeMember
|
|
? expectedNativeMemberName ?? memberEntity.name
|
|
: (isJsInteropMember
|
|
? expectedJsInteropMemberName ?? memberEntity.name
|
|
: null),
|
|
nativeData.getFixedBackendName(memberEntity),
|
|
"Unexpected fixed backend name from native data for $member, "
|
|
"id: $memberId");
|
|
|
|
if (expectedCreatesText != null) {
|
|
String createsText;
|
|
if (memberEntity.isField) {
|
|
createsText = nativeData
|
|
.getNativeFieldLoadBehavior(memberEntity)
|
|
.typesInstantiated
|
|
.join(',');
|
|
} else {
|
|
createsText = nativeData
|
|
.getNativeMethodBehavior(memberEntity)
|
|
.typesInstantiated
|
|
.join(',');
|
|
}
|
|
Expect.equals(
|
|
expectedCreatesText,
|
|
createsText,
|
|
"Unexpected create annotations from native data for $member, "
|
|
"id: $memberId");
|
|
}
|
|
|
|
if (expectedReturnsText != null) {
|
|
String returnsText;
|
|
if (memberEntity.isField) {
|
|
returnsText = nativeData
|
|
.getNativeFieldLoadBehavior(memberEntity)
|
|
.typesReturned
|
|
.join(',');
|
|
} else {
|
|
returnsText = nativeData
|
|
.getNativeMethodBehavior(memberEntity)
|
|
.typesReturned
|
|
.join(',');
|
|
}
|
|
Expect.equals(
|
|
expectedReturnsText,
|
|
returnsText,
|
|
"Unexpected returns annotations from native data for $member, "
|
|
"id: $memberId");
|
|
}
|
|
|
|
List<PragmaAnnotationData> pragmaAnnotations = frontendStrategy
|
|
.modularStrategyForTesting
|
|
.getPragmaAnnotationData(member);
|
|
Set<String> pragmaNames =
|
|
pragmaAnnotations.map((d) => d.name).toSet();
|
|
Expect.setEquals(
|
|
expectedPragmaNames,
|
|
pragmaNames,
|
|
"Unexpected pragmas from modular strategy for $member, "
|
|
"id: $memberId");
|
|
}
|
|
|
|
for (ir.Library library in component.libraries) {
|
|
if (library.importUri.scheme == 'memory') {
|
|
String libraryId = library.importUri.path;
|
|
LibraryEntity libraryEntity = elementMap.getLibrary(library);
|
|
|
|
String expectedJsInteropLibraryName =
|
|
expectedJsInteropLibraryNames[libraryId];
|
|
if (useIr) {
|
|
Expect.equals(
|
|
expectedJsInteropLibraryName,
|
|
annotationData.getJsInteropLibraryName(library),
|
|
"Unexpected js library name from IR for $library");
|
|
}
|
|
Expect.equals(
|
|
expectedJsInteropLibraryName != null,
|
|
nativeData.isJsInteropLibrary(libraryEntity),
|
|
"Unexpected js library result from native data for $library");
|
|
Expect.equals(
|
|
expectedJsInteropLibraryName,
|
|
nativeData.getJsInteropLibraryName(libraryEntity),
|
|
"Unexpected js library name from native data for $library");
|
|
|
|
for (ir.Class cls in library.classes) {
|
|
String clsId = '$libraryId::${cls.name}';
|
|
ClassEntity classEntity = elementMap.getClass(cls);
|
|
|
|
String expectedNativeClassName = expectedNativeClassNames[clsId];
|
|
if (useIr) {
|
|
Expect.equals(
|
|
expectedNativeClassName,
|
|
annotationData.getNativeClassName(cls),
|
|
"Unexpected native class name from IR for $cls");
|
|
}
|
|
bool isNativeClass = nativeData.isNativeClass(classEntity) &&
|
|
!nativeData.isJsInteropClass(classEntity);
|
|
String nativeDataClassName;
|
|
if (isNativeClass) {
|
|
nativeDataClassName =
|
|
nativeData.getNativeTagsOfClass(classEntity).join(',');
|
|
if (nativeData.hasNativeTagsForcedNonLeaf(classEntity)) {
|
|
nativeDataClassName += ',!nonleaf';
|
|
}
|
|
}
|
|
Expect.equals(expectedNativeClassName != null, isNativeClass,
|
|
"Unexpected native class result from native data for $cls");
|
|
|
|
Expect.equals(expectedNativeClassName, nativeDataClassName,
|
|
"Unexpected native class name from native data for $cls");
|
|
|
|
String expectedJsInteropClassName =
|
|
expectedJsInteropClassNames[clsId];
|
|
if (useIr) {
|
|
Expect.equals(
|
|
expectedJsInteropClassName,
|
|
annotationData.getJsInteropClassName(cls),
|
|
"Unexpected js class name from IR for $cls");
|
|
}
|
|
Expect.equals(
|
|
expectedJsInteropClassName != null,
|
|
nativeData.isJsInteropClass(classEntity),
|
|
"Unexpected js class result from native data for $cls");
|
|
Expect.equals(
|
|
expectedJsInteropClassName,
|
|
nativeData.getJsInteropClassName(classEntity),
|
|
"Unexpected js class name from native data for $cls");
|
|
|
|
bool expectedAnonymousJsInteropClass =
|
|
expectedAnonymousJsInteropClasses.contains(clsId);
|
|
if (useIr) {
|
|
Expect.equals(
|
|
expectedAnonymousJsInteropClass,
|
|
annotationData.isAnonymousJsInteropClass(cls),
|
|
"Unexpected js anonymous class result from IR for $cls");
|
|
}
|
|
Expect.equals(
|
|
expectedAnonymousJsInteropClass,
|
|
nativeData.isAnonymousJsInteropClass(classEntity),
|
|
"Unexpected js anonymousclass result from native data for "
|
|
"$cls");
|
|
|
|
for (ir.Member member in cls.members) {
|
|
testMember(clsId, member,
|
|
implicitJsInteropMember:
|
|
nativeData.isJsInteropClass(classEntity),
|
|
implicitNativeMember: member is! ir.Constructor &&
|
|
nativeData.isNativeClass(classEntity) &&
|
|
!nativeData.isJsInteropClass(classEntity));
|
|
}
|
|
}
|
|
for (ir.Member member in library.members) {
|
|
testMember(libraryId, member,
|
|
implicitJsInteropMember: expectedJsInteropLibraryName != null,
|
|
implicitNativeMember: false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
testAll(compiler.frontendClosedWorldForTesting.nativeData);
|
|
if (useIr) {
|
|
// We need to open the environment because creating annotation data
|
|
// from IR will create K-model classes and members for all annotations
|
|
// in the IR component, and not just the ones queried specifically for
|
|
// JS-interop and pragma-like annotations.
|
|
elementMap.envIsClosed = false;
|
|
testAll(new NativeDataImpl.fromIr(elementMap, annotationData));
|
|
}
|
|
}
|
|
|
|
print('test annotations from K-model');
|
|
await runTest(useIr: false);
|
|
|
|
print('test annotations from IR');
|
|
await runTest(useIr: true);
|
|
});
|
|
}
|