diff --git a/demos/java/.project b/demos/java/.project
index f1e2727..2e6ed36 100644
--- a/demos/java/.project
+++ b/demos/java/.project
@@ -1,17 +1,28 @@
-
+
- java
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
\ No newline at end of file
+ java
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
+
+ 1599063072264
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
+
diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts
index 40e08d5..126b1f0 100644
--- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts
+++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts
@@ -1,6 +1,8 @@
import {
DataExtractionResult,
DataExtractorId,
+ GraphNode,
+ GraphVisualizationData,
} from "@hediet/debug-visualizer-data-extraction";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import {
@@ -38,13 +40,36 @@ export class GenericEvaluator implements Evaluator {
expression,
preferredExtractorId,
});
- let reply;
+ let reply: { result: string, variablesReference: number };
try {
reply = await this.session.evaluate({
expression: finalExpression,
frameId,
context: this.getContext(),
});
+
+ // Use structural information about variables
+ // from the evaluation response if present.
+ if (reply.variablesReference) {
+ const graph = await this.constructGraphFromVariablesReference(reply.result, reply.variablesReference);
+
+ return {
+ kind: "data",
+ result: {
+ availableExtractors: [],
+ usedExtractor: {
+ id: "generic" as any,
+ name: "Generic",
+ priority: 1,
+ },
+ data: graph,
+ },
+ }
+ } else {
+ return parseEvaluationResultFromGenericDebugAdapter(reply.result, {
+ debugAdapterType: this.session.session.configuration.type,
+ });
+ }
} catch (error) {
return {
kind: "error",
@@ -65,10 +90,87 @@ export class GenericEvaluator implements Evaluator {
},
};
}
+ }
- return parseEvaluationResultFromGenericDebugAdapter(reply.result, {
- debugAdapterType: this.session.session.configuration.type,
- });
+ /**
+ * Constructs GraphVisualizationData from a DAP variables
+ * reference by successively querying the debug adapter for
+ * variables. Objects are considered to be equivalent if
+ * they share the same variables reference (this is important
+ * for representing cyclic relationships).
+ *
+ * @param rootLabel - The root object's label
+ * @param rootVariablesReference - The root object's DAP variables reference
+ * @param maxDepth - The maximum depth to search at
+ * @param maxKnownNodes - The maximum number of nodes
+ */
+ private async constructGraphFromVariablesReference(
+ rootLabel: string,
+ rootVariablesReference: number,
+ maxDepth: number = 30,
+ maxKnownNodes: number = 50,
+ ): Promise {
+ // Perform a breadth-first search on the object to construct the graph
+
+ const graph: GraphVisualizationData = {
+ kind: { graph: true },
+ nodes: [],
+ edges: []
+ };
+ const knownNodeIds: { [ref: number]: string; } = {};
+ const bfsQueue: { source: { id: string, name: string } | undefined, label: string, variablesReference: number, depth: number }[] = [{
+ source: undefined,
+ label: rootLabel,
+ variablesReference: rootVariablesReference,
+ depth: 0,
+ }];
+
+ let knownCount: number = 0;
+
+ do {
+ const variable = bfsQueue.shift()!;
+ const hasChilds = variable.variablesReference > 0;
+
+ if (variable.depth > maxDepth) {
+ break;
+ }
+
+ let nodeId: string;
+
+ if (!hasChilds || !(variable.variablesReference in knownNodeIds)) {
+ // The variable is a leaf or an unvisited object: create the node.
+
+ const node: GraphNode = {
+ id: hasChilds ? `${variable.variablesReference}` : `__${variable.label}@${knownCount}__`,
+ label: variable.label,
+ color: variable.depth == 0 ? "lightblue" : undefined,
+ shape: "box",
+ };
+
+ graph.nodes.push(node);
+ knownCount++;
+
+ if (hasChilds) {
+ knownNodeIds[variable.variablesReference] = node.id;
+
+ for (const child of await this.session.getVariables({ variablesReference: variable.variablesReference })) {
+ bfsQueue.push({ source: { id: node.id, name: child.name }, label: child.value, variablesReference: child.variablesReference, depth: variable.depth + 1 });
+ }
+ }
+
+ nodeId = node.id;
+ } else {
+ // The variable is a visited object (e.g. due to a cyclic reference)
+
+ nodeId = knownNodeIds[variable.variablesReference];
+ }
+
+ if (variable.source) {
+ graph.edges.push({ from: variable.source.id, to: nodeId, label: variable.source.name });
+ }
+ } while (bfsQueue.length > 0 && knownCount <= maxKnownNodes);
+
+ return graph;
}
protected getFinalExpression(args: {
diff --git a/extension/src/debugger/EnhancedDebugSession.ts b/extension/src/debugger/EnhancedDebugSession.ts
index 4cf7a3c..5e8a00e 100644
--- a/extension/src/debugger/EnhancedDebugSession.ts
+++ b/extension/src/debugger/EnhancedDebugSession.ts
@@ -46,6 +46,23 @@ export class EnhancedDebugSession {
}
}
+ public async getVariables(args: {
+ variablesReference: number
+ }): Promise {
+ try {
+ const reply = await this.session.customRequest("variables", {
+ variablesReference: args.variablesReference
+ });
+ if (!reply) {
+ return [];
+ }
+ return reply.variables;
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ }
+
/**
* Evaluates the given expression.
* If context is "watch", long results are usually shortened.
@@ -55,15 +72,22 @@ export class EnhancedDebugSession {
expression: string;
frameId: number | undefined;
context: "watch" | "repl" | "copy";
- }): Promise<{ result: string }> {
+ }): Promise<{ result: string, variablesReference: number }> {
const reply = await this.session.customRequest("evaluate", {
expression: args.expression,
frameId: args.frameId,
context: args.context,
});
- return { result: reply.result };
+ return { result: reply.result, variablesReference: reply.variablesReference };
}
}
+
+interface Variable {
+ name: string;
+ value: string;
+ variablesReference: number;
+}
+
interface StackFrame {
id: number;
name: string;