// 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_backend/namer.dart'; 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 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 { 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> 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 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 { final JsToElementMap _elementMap; final ClosureData _closureDataLookup; final ProgramLookup _programLookup; ModelIrComputer( DiagnosticReporter reporter, Map> 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 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; fixedNameCall = selector is StringBackedName; } 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 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 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; }