| // 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 'dart:io'; |
| import 'package:_fe_analyzer_shared/src/testing/features.dart'; |
| import 'package:async_helper/async_helper.dart'; |
| import 'package:compiler/src/closure.dart'; |
| import 'package:compiler/src/common.dart'; |
| import 'package:compiler/src/common/codegen.dart'; |
| import 'package:compiler/src/compiler.dart'; |
| import 'package:compiler/src/diagnostics/diagnostic_listener.dart'; |
| import 'package:compiler/src/elements/entities.dart'; |
| import 'package:compiler/src/js/js.dart' as js; |
| import 'package:compiler/src/js_emitter/model.dart'; |
| import 'package:compiler/src/js_model/element_map.dart'; |
| import 'package:compiler/src/js_model/js_world.dart'; |
| import 'package:js_ast/js_ast.dart' as js; |
| import 'package:kernel/ast.dart' as ir; |
| import '../equivalence/id_equivalence.dart'; |
| import '../equivalence/id_equivalence_helper.dart'; |
| import '../helpers/program_lookup.dart'; |
| |
| main(List<String> args) { |
| asyncTest(() async { |
| Directory dataDir = |
| new Directory.fromUri(Platform.script.resolve('model_data')); |
| await checkTests(dataDir, const ModelDataComputer(), |
| args: args, testedConfigs: allInternalConfigs); |
| }); |
| } |
| |
| class ModelDataComputer extends DataComputer<Features> { |
| const ModelDataComputer(); |
| |
| /// Compute type inference data for [member] from kernel based inference. |
| /// |
| /// Fills [actualMap] with the data. |
| @override |
| void computeMemberData(Compiler compiler, MemberEntity member, |
| Map<Id, ActualData<Features>> actualMap, |
| {bool verbose: false}) { |
| JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting; |
| JsToElementMap elementMap = closedWorld.elementMap; |
| MemberDefinition definition = elementMap.getMemberDefinition(member); |
| new ModelIrComputer(compiler.reporter, actualMap, elementMap, member, |
| compiler, closedWorld.closureDataLookup) |
| .run(definition.node); |
| } |
| |
| @override |
| DataInterpreter<Features> get dataValidator => |
| const FeaturesDataInterpreter(wildcard: '*'); |
| } |
| |
| class Tags { |
| static const String needsCheckedSetter = 'checked'; |
| static const String getterFlags = 'get'; |
| static const String setterFlags = 'set'; |
| static const String parameterCount = 'params'; |
| static const String call = 'calls'; |
| static const String parameterStub = 'stubs'; |
| static const String callStubCall = 'stubCalls'; |
| static const String callStubAccesses = 'stubAccesses'; |
| static const String isEmitted = 'emitted'; |
| static const String isElided = 'elided'; |
| static const String assignment = 'assign'; |
| static const String isLazy = 'lazy'; |
| static const String propertyAccess = 'access'; |
| static const String switchCase = 'switch'; |
| } |
| |
| /// AST visitor for computing inference data for a member. |
| class ModelIrComputer extends IrDataExtractor<Features> { |
| final JsToElementMap _elementMap; |
| final ClosureData _closureDataLookup; |
| final ProgramLookup _programLookup; |
| |
| ModelIrComputer( |
| DiagnosticReporter reporter, |
| Map<Id, ActualData<Features>> actualMap, |
| this._elementMap, |
| MemberEntity member, |
| Compiler compiler, |
| this._closureDataLookup) |
| : _programLookup = new ProgramLookup(compiler.backendStrategy), |
| super(reporter, actualMap); |
| |
| void registerCalls(Features features, String tag, js.Node node, |
| {String prefix = '', Set<js.PropertyAccess> handledAccesses}) { |
| forEachNode(node, onCall: (js.Call node) { |
| js.Node target = undefer(node.target); |
| if (target is js.PropertyAccess) { |
| js.Node selector = undefer(target.selector); |
| bool fixedNameCall = false; |
| String name; |
| if (selector is js.Name) { |
| name = selector.key; |
| } else if (selector is js.LiteralString) { |
| /// Call to fixed backend name, so we include the argument |
| /// values to test encoding of optional parameters in native |
| /// methods. |
| name = selector.value.substring(1, selector.value.length - 1); |
| fixedNameCall = true; |
| } |
| if (name != null) { |
| if (fixedNameCall) { |
| String arguments = node.arguments.map(js.nodeToString).join(','); |
| features.addElement(tag, '${prefix}${name}(${arguments})'); |
| } else { |
| features.addElement( |
| tag, '${prefix}${name}(${node.arguments.length})'); |
| } |
| handledAccesses?.add(target); |
| } |
| } |
| }); |
| } |
| |
| void registerAccesses(Features features, String tag, js.Node code, |
| {String prefix = '', Set<js.PropertyAccess> handledAccesses}) { |
| forEachNode(code, onPropertyAccess: (js.PropertyAccess node) { |
| if (handledAccesses?.contains(node) ?? false) { |
| return; |
| } |
| |
| js.Node receiver = undefer(node.receiver); |
| String receiverName; |
| if (receiver is js.VariableUse) { |
| receiverName = receiver.name; |
| if (receiverName == receiverName.toUpperCase() && |
| receiverName != r'$') { |
| // Skip holder access. |
| receiverName = null; |
| } |
| } else if (receiver is js.This) { |
| receiverName = 'this'; |
| } |
| |
| js.Node selector = undefer(node.selector); |
| String name; |
| if (selector is js.Name) { |
| name = selector.key; |
| } else if (selector is js.LiteralString) { |
| /// Call to fixed backend name, so we include the argument |
| /// values to test encoding of optional parameters in native |
| /// methods. |
| name = selector.value.substring(1, selector.value.length - 1); |
| } |
| |
| if (receiverName != null && name != null) { |
| features.addElement(tag, '${prefix}${name}'); |
| } |
| }); |
| } |
| |
| Features getMemberValue(MemberEntity member) { |
| if (member is FieldEntity) { |
| Field field = _programLookup.getField(member); |
| if (field != null) { |
| Features features = new Features(); |
| if (field.needsCheckedSetter) { |
| features.add(Tags.needsCheckedSetter); |
| } |
| if (field.isElided) { |
| features.add(Tags.isElided); |
| } else { |
| features.add(Tags.isEmitted); |
| } |
| void registerFlags(String tag, int flags) { |
| switch (flags) { |
| case 0: |
| break; |
| case 1: |
| features.add(tag, value: 'simple'); |
| break; |
| case 2: |
| features.add(tag, value: 'intercepted'); |
| break; |
| case 3: |
| features.add(tag, value: 'interceptedThis'); |
| break; |
| } |
| } |
| |
| registerFlags(Tags.getterFlags, field.getterFlags); |
| registerFlags(Tags.setterFlags, field.setterFlags); |
| |
| Class cls = _programLookup.getClass(member.enclosingClass); |
| for (StubMethod stub in cls.callStubs) { |
| if (stub.element == member) { |
| registerCalls(features, Tags.callStubCall, stub.code, |
| prefix: '${stub.name.key}:'); |
| registerAccesses(features, Tags.callStubAccesses, stub.code, |
| prefix: '${stub.name.key}:'); |
| } |
| } |
| |
| return features; |
| } |
| StaticField staticField = _programLookup.getStaticField(member); |
| if (staticField != null) { |
| Features features = new Features(); |
| features.add(Tags.isEmitted); |
| if (staticField.isLazy) { |
| features.add(Tags.isLazy); |
| } |
| return features; |
| } |
| } else if (member is FunctionEntity) { |
| Method method = _programLookup.getMethod(member); |
| if (method != null) { |
| Features features = new Features(); |
| js.Expression code = method.code; |
| if (code is js.Fun) { |
| features[Tags.parameterCount] = '${code.params.length}'; |
| } |
| |
| Set<js.PropertyAccess> handledAccesses = new Set(); |
| |
| registerCalls(features, Tags.call, code, |
| handledAccesses: handledAccesses); |
| if (method is DartMethod) { |
| for (ParameterStubMethod stub in method.parameterStubs) { |
| registerCalls(features, Tags.parameterStub, stub.code, |
| prefix: '${stub.name.key}:', handledAccesses: handledAccesses); |
| } |
| } |
| |
| forEachNode(code, onAssignment: (js.Assignment node) { |
| js.Expression leftHandSide = undefer(node.leftHandSide); |
| if (leftHandSide is js.PropertyAccess) { |
| js.Node selector = undefer(leftHandSide.selector); |
| String name; |
| if (selector is js.Name) { |
| name = selector.key; |
| } else if (selector is js.LiteralString) { |
| name = selector.value.substring(1, selector.value.length - 1); |
| } |
| if (name != null) { |
| features.addElement(Tags.assignment, '${name}'); |
| handledAccesses.add(leftHandSide); |
| } |
| } |
| }); |
| |
| registerAccesses(features, Tags.propertyAccess, code, |
| handledAccesses: handledAccesses); |
| |
| forEachNode(code, onSwitch: (js.Switch node) { |
| features.add(Tags.switchCase); |
| }); |
| |
| return features; |
| } |
| } |
| return null; |
| } |
| |
| @override |
| Features computeMemberValue(Id id, ir.Member node) { |
| return getMemberValue(_elementMap.getMember(node)); |
| } |
| |
| @override |
| Features computeNodeValue(Id id, ir.TreeNode node) { |
| if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) { |
| ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node); |
| return getMemberValue(info.callMethod); |
| } |
| return null; |
| } |
| } |
| |
| js.Node undefer(js.Node node) { |
| if (node is js.DeferredExpression) return undefer(node.value); |
| if (node is ModularName) return undefer(node.value); |
| return node; |
| } |