// 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:expect/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/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: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 = 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, }) { 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 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 = 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; } 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? 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 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; }