| // 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. | |
| import 'dart:io'; | |
| import 'package:_fe_analyzer_shared/src/testing/features.dart'; | |
| import 'package:compiler/src/closure.dart'; | |
| import 'package:compiler/src/common/codegen.dart'; | |
| import 'package:compiler/src/compiler.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:expect/async_helper.dart'; | |
| 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 = 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, | |
| ActualDataMap<Features> actualMap, { | |
| bool verbose = false, | |
| }) { | |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; | |
| JsToElementMap elementMap = closedWorld.elementMap; | |
| MemberDefinition definition = elementMap.getMemberDefinition(member); | |
| 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( | |
| super.reporter, | |
| super.actualMap, | |
| this._elementMap, | |
| MemberEntity member, | |
| Compiler compiler, | |
| this._closureDataLookup, | |
| ) : _programLookup = ProgramLookup(compiler.backendStrategy); | |
| 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; | |
| 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; | |
| } | |
| 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 = 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 = 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 = Features(); | |
| js.Expression code = method.code; | |
| if (code is js.Fun) { | |
| features[Tags.parameterCount] = '${code.params.length}'; | |
| } | |
| Set<js.PropertyAccess> handledAccesses = 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) { | |
| final 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; | |
| } | |
| 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 as ir.LocalFunction, | |
| ); | |
| 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; | |
| } |