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]); |
- } |
-} |