blob: 78c02dcb8b1c1d0e2426c7d0f697c2a519475178 [file] [log] [blame]
// Copyright (c) 2017, 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' hide Link;
import 'package:_fe_analyzer_shared/src/testing/features.dart';
import 'package:_fe_analyzer_shared/src/util/link.dart' show Link;
import 'package:async_helper/async_helper.dart';
import 'package:compiler/src/closure.dart';
import 'package:compiler/src/common.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/js_model/element_map.dart';
import 'package:compiler/src/js_model/js_world.dart';
import 'package:compiler/src/js_model/locals.dart';
import 'package:compiler/src/world.dart';
import 'package:expect/expect.dart';
import 'package:kernel/ast.dart' as ir;
import '../equivalence/id_equivalence.dart';
import '../equivalence/id_equivalence_helper.dart';
main(List<String> args) {
asyncTest(() async {
Directory dataDir = new Directory.fromUri(Platform.script.resolve('data'));
await checkTests(dataDir, const ClosureDataComputer(), args: args);
});
}
class ClosureDataComputer extends DataComputer<String> {
const ClosureDataComputer();
@override
void computeMemberData(Compiler compiler, MemberEntity member,
Map<Id, ActualData<String>> actualMap,
{bool verbose: false}) {
JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
JsToElementMap elementMap = closedWorld.elementMap;
GlobalLocalsMap localsMap =
compiler.globalInference.resultsForTesting.globalLocalsMap;
ClosureData closureDataLookup = closedWorld.closureDataLookup;
MemberDefinition definition = elementMap.getMemberDefinition(member);
assert(
definition.kind == MemberKind.regular ||
definition.kind == MemberKind.constructor,
failedAt(member, "Unexpected member definition $definition"));
new ClosureIrChecker(compiler.reporter, actualMap, elementMap, member,
localsMap.getLocalsMap(member), closureDataLookup, closedWorld,
verbose: verbose)
.run(definition.node);
}
@override
DataInterpreter<String> get dataValidator => const StringDataInterpreter();
}
/// Kernel IR visitor for computing closure data.
class ClosureIrChecker extends IrDataExtractor<String> {
final MemberEntity member;
final ClosureData closureDataLookup;
final JClosedWorld _closedWorld;
final KernelToLocalsMap _localsMap;
final bool verbose;
Map<BoxLocal, String> boxNames = <BoxLocal, String>{};
Link<ScopeInfo> scopeInfoStack = const Link<ScopeInfo>();
Link<CapturedScope> capturedScopeStack = const Link<CapturedScope>();
Link<ClosureRepresentationInfo> closureRepresentationInfoStack =
const Link<ClosureRepresentationInfo>();
ClosureIrChecker(
DiagnosticReporter reporter,
Map<Id, ActualData<String>> actualMap,
JsToElementMap elementMap,
this.member,
this._localsMap,
this.closureDataLookup,
this._closedWorld,
{this.verbose: false})
: super(reporter, actualMap) {
pushMember(member);
}
ScopeInfo get scopeInfo => scopeInfoStack.head;
CapturedScope get capturedScope => capturedScopeStack.head;
ClosureRepresentationInfo get closureRepresentationInfo =>
closureRepresentationInfoStack.isNotEmpty
? closureRepresentationInfoStack.head
: null;
@override
visitFunctionExpression(ir.FunctionExpression node) {
ClosureRepresentationInfo info = closureDataLookup.getClosureInfo(node);
pushMember(info.callMethod);
pushLocalFunction(node);
super.visitFunctionExpression(node);
popLocalFunction();
popMember();
}
@override
visitFunctionDeclaration(ir.FunctionDeclaration node) {
ClosureRepresentationInfo info = closureDataLookup.getClosureInfo(node);
pushMember(info.callMethod);
pushLocalFunction(node);
super.visitFunctionDeclaration(node);
popLocalFunction();
popMember();
}
@override
visitForStatement(ir.ForStatement node) {
pushLoopNode(node);
super.visitForStatement(node);
popLoop();
}
@override
visitWhileStatement(ir.WhileStatement node) {
pushLoopNode(node);
super.visitWhileStatement(node);
popLoop();
}
@override
visitForInStatement(ir.ForInStatement node) {
pushLoopNode(node);
super.visitForInStatement(node);
popLoop();
}
@override
String computeNodeValue(Id id, ir.Node node) {
if (node is ir.VariableDeclaration) {
Local local = _localsMap.getLocalVariable(node);
return computeLocalValue(local);
} else if (node is ir.FunctionDeclaration) {
ClosureRepresentationInfo info = closureDataLookup.getClosureInfo(node);
return computeObjectValue(info.callMethod);
} else if (node is ir.FunctionExpression) {
ClosureRepresentationInfo info = closureDataLookup.getClosureInfo(node);
return computeObjectValue(info.callMethod);
}
return null;
}
@override
String computeMemberValue(Id id, ir.Member node) {
return computeObjectValue(member);
}
void pushMember(MemberEntity member) {
scopeInfoStack =
scopeInfoStack.prepend(closureDataLookup.getScopeInfo(member));
capturedScopeStack =
capturedScopeStack.prepend(closureDataLookup.getCapturedScope(member));
if (capturedScope.requiresContextBox) {
boxNames[capturedScope.contextBox] = 'box${boxNames.length}';
}
dump(member);
}
void popMember() {
scopeInfoStack = scopeInfoStack.tail;
capturedScopeStack = capturedScopeStack.tail;
}
void pushLoopNode(ir.Node node) {
//scopeInfoStack = // TODO?
// scopeInfoStack.prepend(closureDataLookup.getScopeInfo(member));
capturedScopeStack = capturedScopeStack
.prepend(closureDataLookup.getCapturedLoopScope(node));
if (capturedScope.requiresContextBox) {
boxNames[capturedScope.contextBox] = 'box${boxNames.length}';
}
dump(node);
}
void popLoop() {
capturedScopeStack = capturedScopeStack.tail;
}
void pushLocalFunction(ir.Node node) {
closureRepresentationInfoStack = closureRepresentationInfoStack
.prepend(closureDataLookup.getClosureInfo(node));
dump(node);
}
void popLocalFunction() {
closureRepresentationInfoStack = closureRepresentationInfoStack.tail;
}
void dump(Object object) {
if (!verbose) return;
print('object: $object');
if (object is MemberEntity) {
print(' capturedScope (${capturedScope.runtimeType})');
capturedScope.forEachBoxedVariable(
_localsMap, (a, b) => print(' boxed: $a->$b'));
}
print(
' closureRepresentationInfo (${closureRepresentationInfo.runtimeType})');
closureRepresentationInfo?.forEachFreeVariable(
_localsMap, (a, b) => print(' free: $a->$b'));
closureRepresentationInfo?.forEachBoxedVariable(
_localsMap, (a, b) => print(' boxed: $a->$b'));
}
/// Compute a string representation of the data stored for [local] in [info].
String computeLocalValue(Local local) {
Features features = new Features();
if (scopeInfo.localIsUsedInTryOrSync(_localsMap, local)) {
features.add('inTry');
// TODO(johnniwinther,efortuna): Should this be enabled and checked?
//Expect.isTrue(capturedScope.localIsUsedInTryOrSync(local));
} else {
//Expect.isFalse(capturedScope.localIsUsedInTryOrSync(local));
}
if (capturedScope.isBoxedVariable(_localsMap, local)) {
features.add('boxed');
}
if (capturedScope.contextBox == local) {
// TODO(johnniwinther): This shouldn't happen! Remove branch/throw error
// when we verify it can't happen.
features.add('error-box');
}
if (capturedScope is CapturedLoopScope) {
CapturedLoopScope loopScope = capturedScope;
if (loopScope.getBoxedLoopVariables(_localsMap).contains(local)) {
features.add('loop');
}
}
// TODO(johnniwinther,efortuna): Add more info?
return features.getText();
}
String computeObjectValue(MemberEntity member) {
Features features = new Features();
void addLocals(
String name, forEach(KernelToLocalsMap localsMap, f(Local local, _))) {
List<String> names = <String>[];
forEach(_localsMap, (Local local, _) {
if (local is BoxLocal) {
names.add(boxNames[local]);
} else {
names.add(local.name);
}
});
String value = names.isEmpty ? null : '[${(names..sort()).join(',')}]';
if (features.containsKey(name)) {
Expect.equals(
features[name], value, "Inconsistent values for $name on $member.");
}
features[name] = value;
}
if (scopeInfo.thisLocal != null) {
features['hasThis'] = '';
}
if (capturedScope.requiresContextBox) {
var keyword = 'boxed';
addLocals(keyword, capturedScope.forEachBoxedVariable);
features['box'] = '(${boxNames[capturedScope.contextBox]} which holds '
'${features[keyword]})';
features.remove(keyword);
}
if (closureRepresentationInfo != null) {
addLocals('free', closureRepresentationInfo.forEachFreeVariable);
if (closureRepresentationInfo.closureClassEntity != null) {
addLocals('fields', (KernelToLocalsMap localsMap, f(Local local, _)) {
_closedWorld.elementEnvironment.forEachInstanceField(
closureRepresentationInfo.closureClassEntity,
(_, FieldEntity field) {
if (_closedWorld.fieldAnalysis.getFieldData(field).isElided) return;
f(closureRepresentationInfo.getLocalForField(localsMap, field),
field);
});
});
}
}
return features.getText();
}
}