| Index: pkg/kernel/lib/type_propagation/visualizer.dart
|
| diff --git a/pkg/kernel/lib/type_propagation/visualizer.dart b/pkg/kernel/lib/type_propagation/visualizer.dart
|
| deleted file mode 100644
|
| index 8cfd86a5ca42b48151eb0db084bb3a3c9adffdc1..0000000000000000000000000000000000000000
|
| --- a/pkg/kernel/lib/type_propagation/visualizer.dart
|
| +++ /dev/null
|
| @@ -1,395 +0,0 @@
|
| -// Copyright (c) 2016, 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.
|
| -library kernel.type_propagation.visualizer;
|
| -
|
| -import 'constraints.dart';
|
| -import 'builder.dart';
|
| -import 'solver.dart';
|
| -import '../ast.dart';
|
| -import '../text/ast_to_text.dart';
|
| -import '../class_hierarchy.dart';
|
| -
|
| -/// Visualizes the constraint system using a Graphviz dot graph.
|
| -///
|
| -/// Variables are visualized as nodes and constraints as labeled edges.
|
| -class Visualizer {
|
| - final Program program;
|
| - final Map<int, GraphNode> variableNodes = <int, GraphNode>{};
|
| - final Map<int, FunctionNode> value2function = <int, FunctionNode>{};
|
| - final Map<FunctionNode, int> function2value = <FunctionNode, int>{};
|
| - final Map<int, Annotation> latticePointAnnotation = <int, Annotation>{};
|
| - final Map<int, Annotation> valueAnnotation = <int, Annotation>{};
|
| - FieldNames fieldNames;
|
| - ConstraintSystem constraints;
|
| - Solver solver;
|
| - Builder builder;
|
| -
|
| - ClassHierarchy get hierarchy => builder.hierarchy;
|
| -
|
| - final Map<Member, Set<GraphNode>> _graphNodesInMember =
|
| - <Member, Set<GraphNode>>{};
|
| -
|
| - Visualizer(this.program);
|
| -
|
| - static Set<GraphNode> _makeGraphNodeSet() => new Set<GraphNode>();
|
| -
|
| - Annotator getTextAnnotator() {
|
| - return new TextAnnotator(this);
|
| - }
|
| -
|
| - GraphNode getVariableNode(int variable) {
|
| - return variableNodes[variable] ??= new GraphNode(variable);
|
| - }
|
| -
|
| - /// Called from the builder to associate information with a variable.
|
| - ///
|
| - /// The [node] has two purposes: it ensures that the variable will show
|
| - /// up in the graph for a the enclosing member, and the textual form of the
|
| - /// node will be part of its label.
|
| - ///
|
| - /// The optional [info] argument provides additional context beyond the AST
|
| - /// node. When a constraint variable has no logical 1:1 corresondence with
|
| - /// an AST node, it is best to pick a nearby AST node and set the [info] to
|
| - /// clarify its relationship with the node.
|
| - void annotateVariable(int variable, TreeNode astNode, [String info]) {
|
| - if (astNode != null || info != null) {
|
| - if (astNode is VariableSet ||
|
| - astNode is PropertySet ||
|
| - astNode is StaticSet) {
|
| - // These will also be registered for the right-hand side, which makes
|
| - // for a better annotation.
|
| - return;
|
| - }
|
| - var node = getVariableNode(variable);
|
| - Member member = _getEnclosingMember(astNode);
|
| - node.addAnnotation(member, astNode, info);
|
| - _graphNodesInMember.putIfAbsent(member, _makeGraphNodeSet).add(node);
|
| - }
|
| - }
|
| -
|
| - void annotateAssign(int source, int destination, TreeNode node) {
|
| - addEdge(source, destination, _getEnclosingMember(node), '');
|
| - }
|
| -
|
| - void annotateSink(int source, int destination, TreeNode node) {
|
| - addEdge(source, destination, _getEnclosingMember(node), 'sink');
|
| - }
|
| -
|
| - void annotateLoad(int object, int field, int destination, Member member) {
|
| - String fieldName = fieldNames.getDiagnosticNameOfField(field);
|
| - addEdge(object, destination, member, 'Load[$fieldName]');
|
| - }
|
| -
|
| - void annotateStore(int object, int field, int source, Member member) {
|
| - String fieldName = fieldNames.getDiagnosticNameOfField(field);
|
| - addEdge(source, object, member, 'Store[$fieldName]');
|
| - }
|
| -
|
| - void annotateDirectStore(int object, int field, int source, Member member) {
|
| - String fieldName = fieldNames.getDiagnosticNameOfField(field);
|
| - addEdge(source, object, member, 'Store![$fieldName]');
|
| - }
|
| -
|
| - void annotateLatticePoint(int point, TreeNode node, [String info]) {
|
| - latticePointAnnotation[point] = new Annotation(node, info);
|
| - }
|
| -
|
| - void annotateValue(int value, TreeNode node, [String info]) {
|
| - valueAnnotation[value] = new Annotation(node, info);
|
| - }
|
| -
|
| - String getLatticePointName(int latticePoint) {
|
| - if (latticePoint < 0) return 'bottom';
|
| - return latticePointAnnotation[latticePoint].toLabel();
|
| - }
|
| -
|
| - String getValueName(int value) {
|
| - return valueAnnotation[value].toLabel();
|
| - }
|
| -
|
| - static Member _getEnclosingMember(TreeNode node) {
|
| - while (node != null) {
|
| - if (node is Member) return node;
|
| - node = node.parent;
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - void addEdge(int source, int destination, Member member, String label) {
|
| - var sourceNode = getVariableNode(source);
|
| - var destinationNode = getVariableNode(destination);
|
| - _graphNodesInMember.putIfAbsent(member, _makeGraphNodeSet)
|
| - ..add(sourceNode)
|
| - ..add(destinationNode);
|
| - sourceNode.addEdgeTo(destinationNode, member, label);
|
| - }
|
| -
|
| - void annotateFunction(int value, FunctionNode function) {
|
| - value2function[value] = function;
|
| - function2value[function] = value;
|
| - }
|
| -
|
| - FunctionNode getFunctionFromValue(int value) {
|
| - return value2function[value];
|
| - }
|
| -
|
| - int getFunctionValue(FunctionNode node) {
|
| - return function2value[node];
|
| - }
|
| -
|
| - Set<GraphNode> _getNodesInMember(Member member) {
|
| - return _graphNodesInMember.putIfAbsent(member, _makeGraphNodeSet);
|
| - }
|
| -
|
| - String _getCodeAsLabel(Member member) {
|
| - String code = debugNodeToString(member);
|
| - code = escapeLabel(code);
|
| - // Replace line-breaks with left-aligned breaks.
|
| - code = code.replaceAll('\n', '\\l');
|
| - return code;
|
| - }
|
| -
|
| - String _getValueLabel(GraphNode node) {
|
| - int latticePoint = solver.getVariableValue(node.variable);
|
| - if (latticePoint < 0) return 'bottom';
|
| - return escapeLabel(shorten(getLatticePointName(latticePoint)));
|
| - }
|
| -
|
| - /// Returns the Graphviz Dot code a the subgraph relevant for [member].
|
| - String dumpMember(Member member) {
|
| - int freshIdCounter = 0;
|
| - StringBuffer buffer = new StringBuffer();
|
| - buffer.writeln('digraph {');
|
| - String source = _getCodeAsLabel(member);
|
| - buffer.writeln('source [shape=box,label="$source"]');
|
| - for (GraphNode node in _getNodesInMember(member)) {
|
| - int id = node.variable;
|
| - String label = node.getAnnotationInContextOf(member);
|
| - // Global nodes have a ton of edges that are visualized specially.
|
| - // If the global node has a local annotation, also print its annotated
|
| - // version somewhere, but omit all its edges.
|
| - if (node.isGlobal) {
|
| - if (label != '') {
|
| - label += '\n${node.globalAnnotation.toLabel()}';
|
| - buffer.writeln('$id [shape=record,label="$label"]');
|
| - }
|
| - continue;
|
| - }
|
| - String value = _getValueLabel(node);
|
| - buffer.writeln('$id [shape=record,label="{$label|$value}"]');
|
| - // Add outgoing edges.
|
| - // Keep track of all that edges leave the context of the current member
|
| - // ("external edges"). There can be a huge number of these, so we compact
|
| - // them into a single outgoing edge so as not to flood the graph.
|
| - Set<String> outgoingExternalEdgeLabels = new Set<String>();
|
| - for (Edge edge in node.outputs) {
|
| - if (edge.to.isLocal(member)) {
|
| - buffer.writeln('$id -> ${edge.to.variable} [label="${edge.label}"]');
|
| - } else if (outgoingExternalEdgeLabels.length < 3) {
|
| - String annotation = edge.to.externalLabel;
|
| - if (annotation != '') {
|
| - if (edge.label != '') {
|
| - annotation = '${edge.label} → $annotation';
|
| - }
|
| - outgoingExternalEdgeLabels.add(annotation);
|
| - }
|
| - } else if (outgoingExternalEdgeLabels.length == 3) {
|
| - outgoingExternalEdgeLabels.add('...');
|
| - }
|
| - }
|
| - // Emit the outgoing external edge.
|
| - if (outgoingExternalEdgeLabels.isNotEmpty) {
|
| - int freshId = ++freshIdCounter;
|
| - String outLabel = outgoingExternalEdgeLabels.join('\n');
|
| - buffer.writeln('x$freshId [shape=box,style=dotted,label="$outLabel"]');
|
| - buffer.writeln('$id -> x$freshId [style=dotted]');
|
| - }
|
| - // Show ingoing external edges. As before, avoid flooding the graph in
|
| - // case there are too many of them.
|
| - Set<String> ingoingExternalEdgeLabels = new Set<String>();
|
| - for (Edge edge in node.inputs) {
|
| - GraphNode source = edge.from;
|
| - if (source.isLocal(member)) continue;
|
| - String annotation = source.externalLabel;
|
| - if (annotation != '') {
|
| - if (ingoingExternalEdgeLabels.length < 3) {
|
| - if (edge.label != '') {
|
| - annotation = '$annotation → ${edge.label}';
|
| - }
|
| - ingoingExternalEdgeLabels.add(annotation);
|
| - } else {
|
| - ingoingExternalEdgeLabels.add('...');
|
| - break;
|
| - }
|
| - }
|
| - }
|
| - // Emit the ingoing external edge.
|
| - if (ingoingExternalEdgeLabels.isNotEmpty) {
|
| - int freshId = ++freshIdCounter;
|
| - String sourceLabel = ingoingExternalEdgeLabels.join('\n');
|
| - buffer.writeln('x$freshId '
|
| - '[shape=box,style=dotted,label="$sourceLabel"]');
|
| - buffer.writeln('x$freshId -> ${node.variable} [style=dotted]');
|
| - }
|
| - }
|
| - buffer.writeln('}');
|
| - return '$buffer';
|
| - }
|
| -}
|
| -
|
| -class Annotation {
|
| - final TreeNode node;
|
| - final String info;
|
| -
|
| - Annotation(this.node, this.info);
|
| -
|
| - String toLabel() {
|
| - if (node == null && info == null) return '(missing annotation)';
|
| - if (node == null) return escapeLabel(info);
|
| - String label = node is NullLiteral
|
| - ? 'null literal'
|
| - : node is FunctionNode ? shorten('${node.parent}') : shorten('$node');
|
| - if (info != null) {
|
| - label = '$info: $label';
|
| - }
|
| - label = escapeLabel(label);
|
| - return label;
|
| - }
|
| -
|
| - String toLabelWithContext(Member member) {
|
| - String label = toLabel();
|
| - if (node == member) {
|
| - return label;
|
| - } else {
|
| - return '$label in $member';
|
| - }
|
| - }
|
| -}
|
| -
|
| -class GraphNode {
|
| - final int variable;
|
| - final List<Edge> inputs = <Edge>[];
|
| - final List<Edge> outputs = <Edge>[];
|
| - final List<Annotation> annotations = <Annotation>[];
|
| -
|
| - /// The annotation to show when visualized in the context of a given member.
|
| - final Map<Member, Annotation> annotationForContext = <Member, Annotation>{};
|
| -
|
| - GraphNode(this.variable);
|
| -
|
| - bool get isGlobal => annotationForContext.containsKey(null);
|
| - Annotation get globalAnnotation => annotationForContext[null];
|
| - bool isInScope(Member member) => annotationForContext.containsKey(member);
|
| - bool isLocal(Member member) => !isGlobal && isInScope(member);
|
| -
|
| - /// The label to show for the given node when seen from the context of
|
| - /// another member.
|
| - String get externalLabel {
|
| - if (isGlobal) return globalAnnotation.toLabel();
|
| - if (annotationForContext.isEmpty) return '$variable';
|
| - Member member = annotationForContext.keys.first;
|
| - Annotation annotation = annotationForContext[member];
|
| - return '$variable:' + annotation.toLabelWithContext(member);
|
| - }
|
| -
|
| - String getAnnotationInContextOf(Member member) {
|
| - if (annotationForContext.isEmpty) return '';
|
| - Annotation annotation = annotationForContext[member];
|
| - if (annotation != null) return '$variable:' + annotation.toLabel();
|
| - annotation =
|
| - annotationForContext[null] ?? annotationForContext.values.first;
|
| - return '$variable:' + annotation.toLabelWithContext(member);
|
| - }
|
| -
|
| - void addEdgeTo(GraphNode other, Member member, String label) {
|
| - Edge edge = new Edge(this, other, member, label);
|
| - outputs.add(edge);
|
| - other.inputs.add(edge);
|
| - }
|
| -
|
| - void addAnnotation(Member member, TreeNode astNode, String info) {
|
| - var annotation = new Annotation(astNode, info);
|
| - annotations.add(annotation);
|
| - annotationForContext[member] = annotation;
|
| - }
|
| -}
|
| -
|
| -class Edge {
|
| - final GraphNode from, to;
|
| - final Member member;
|
| - final String label;
|
| -
|
| - Edge(this.from, this.to, this.member, this.label);
|
| -}
|
| -
|
| -final RegExp escapeRegexp = new RegExp('["{}<>|]', multiLine: true);
|
| -
|
| -/// Escapes characters in [text] so it can be used as part of a label.
|
| -String escapeLabel(String text) {
|
| - return text.replaceAllMapped(escapeRegexp, (m) => '\\${m.group(0)}');
|
| -}
|
| -
|
| -String shorten(String text) {
|
| - text = text.replaceAll('\n ', ' ').replaceAll('\n', ' ').trim();
|
| - if (text.length > 60) {
|
| - return text.substring(0, 30) + '...' + text.substring(text.length - 27);
|
| - }
|
| - return text;
|
| -}
|
| -
|
| -class TextAnnotator extends Annotator {
|
| - final Visualizer visualizer;
|
| - final Map<VariableDeclaration, int> variables = <VariableDeclaration, int>{};
|
| - final Map<FunctionNode, int> functionReturns = <FunctionNode, int>{};
|
| -
|
| - Builder get builder => visualizer.builder;
|
| -
|
| - String getReference(Node node, Printer printer) {
|
| - if (node is Class) return printer.getClassReference(node);
|
| - if (node is Member) return printer.getMemberReference(node);
|
| - if (node is Library) return printer.getLibraryReference(node);
|
| - return debugNodeToString(node);
|
| - }
|
| -
|
| - String getValueForVariable(Printer printer, int variable) {
|
| - if (variable == null) {
|
| - return '<missing type>';
|
| - }
|
| - var value = visualizer.solver.getValueInferredForVariable(variable);
|
| - return printer.getInferredValueString(value);
|
| - }
|
| -
|
| - TextAnnotator(this.visualizer) {
|
| - // The correspondence between AST and constraint system is exposed by the
|
| - // builder, but only at the level of Members.
|
| - // To get to the correspondence at the statement/expression level, we use
|
| - // the annotation map from the visualizer API.
|
| - // TODO(asgerf): If we use these annotations for testing, the necessary
|
| - // bindings should arguably be part of the API for the Builder.
|
| - visualizer.variableNodes.forEach((int variable, GraphNode node) {
|
| - for (Annotation annotation in node.annotations) {
|
| - if (annotation.node is VariableDeclaration && annotation.info == null) {
|
| - variables[annotation.node] = variable;
|
| - }
|
| - if (annotation.node is FunctionNode && annotation.info == 'return') {
|
| - functionReturns[annotation.node] = variable;
|
| - }
|
| - }
|
| - });
|
| - }
|
| -
|
| - String annotateVariable(Printer printer, VariableDeclaration node) {
|
| - return getValueForVariable(
|
| - printer, builder.global.parameters[node] ?? variables[node]);
|
| - }
|
| -
|
| - String annotateReturn(Printer printer, FunctionNode node) {
|
| - if (node.parent is Constructor) return null;
|
| - return getValueForVariable(printer, builder.global.returns[node]);
|
| - }
|
| -
|
| - String annotateField(Printer printer, Field node) {
|
| - return getValueForVariable(printer, builder.global.fields[node]);
|
| - }
|
| -}
|
|
|