From fc142926b80c29efd562bdc970830f70c6aca1cf Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 17:04:11 +0200 Subject: [PATCH 01/13] Return 'variablesReference' in EnhancedDebugSession.evaluate --- extension/src/debugger/EnhancedDebugSession.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/src/debugger/EnhancedDebugSession.ts b/extension/src/debugger/EnhancedDebugSession.ts index 4cf7a3c..42e8926 100644 --- a/extension/src/debugger/EnhancedDebugSession.ts +++ b/extension/src/debugger/EnhancedDebugSession.ts @@ -55,13 +55,13 @@ 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 StackFrame { From fa1432ffc87c16344d741e8571f12f9b40e531d9 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 17:10:04 +0200 Subject: [PATCH 02/13] Add EnhancedDebugSession.getVariables --- .../src/debugger/EnhancedDebugSession.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/extension/src/debugger/EnhancedDebugSession.ts b/extension/src/debugger/EnhancedDebugSession.ts index 42e8926..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. @@ -64,6 +81,13 @@ export class EnhancedDebugSession { return { result: reply.result, variablesReference: reply.variablesReference }; } } + +interface Variable { + name: string; + value: string; + variablesReference: number; +} + interface StackFrame { id: number; name: string; From abfa44b4f9addece1cc3b07b2294def04fa12db4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 17:28:08 +0200 Subject: [PATCH 03/13] Add new object construction in GenericEvaluationEngine --- .../GenericEvaluationEngine.ts | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index 40e08d5..e14d07d 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -45,6 +45,27 @@ export class GenericEvaluator implements Evaluator { frameId, context: this.getContext(), }); + + // Use structural information about variables + // from the evaluation response if present. + if (reply.variablesReference) { + return { + kind: "data", + result: { + availableExtractors: [], + usedExtractor: { + id: "generic" as any, + name: "Generic", + priority: 1, + }, + data: await this.constructObjectFromVariablesReference(reply.variablesReference), + }, + } + } else { + return parseEvaluationResultFromGenericDebugAdapter(reply.result, { + debugAdapterType: this.session.session.configuration.type, + }); + } } catch (error) { return { kind: "error", @@ -65,10 +86,18 @@ export class GenericEvaluator implements Evaluator { }, }; } + } - return parseEvaluationResultFromGenericDebugAdapter(reply.result, { - debugAdapterType: this.session.session.configuration.type, - }); + private async constructObjectFromVariablesReference(variablesReference: number): Promise { + var result: any = {}; + for (const child of await this.session.getVariables({ variablesReference })) { + if (child.variablesReference > 0) { + result[child.name] = await this.constructObjectFromVariablesReference(child.variablesReference); + } else { + result[child.name] = child.value; + } + } + return result; } protected getFinalExpression(args: { From 0ce1b02b2ce8d3104cf255a53ea836f1af55ad5f Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 17:33:00 +0200 Subject: [PATCH 04/13] Handle cyclic references in GenericEvaluationEngine --- .../GenericEvaluationEngine.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index e14d07d..ef55821 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -58,7 +58,7 @@ export class GenericEvaluator implements Evaluator { name: "Generic", priority: 1, }, - data: await this.constructObjectFromVariablesReference(reply.variablesReference), + data: await this.constructObjectFromVariablesReference(reply.variablesReference, {}), }, } } else { @@ -88,15 +88,29 @@ export class GenericEvaluator implements Evaluator { } } - private async constructObjectFromVariablesReference(variablesReference: number): Promise { + private async constructObjectFromVariablesReference(variablesReference: number, knownObjects: { [ref: number]: any; }): Promise { var result: any = {}; - for (const child of await this.session.getVariables({ variablesReference })) { - if (child.variablesReference > 0) { - result[child.name] = await this.constructObjectFromVariablesReference(child.variablesReference); + knownObjects[variablesReference] = result; + + for (const variable of await this.session.getVariables({ variablesReference })) { + let child: any; + + if (variable.variablesReference > 0) { + if (variable.variablesReference in knownObjects) { + // If the object is known, we have a (potentially cyclic) reference + child = knownObjects[variable.variablesReference]; + } else { + // Recurse on a new object + child = await this.constructObjectFromVariablesReference(variable.variablesReference, knownObjects); + } } else { - result[child.name] = child.value; + // If there are no childs, just assign the value + child = variable.value; } + + result[variable.name] = child; } + return result; } From 6ef0ecb60fb48d52cf0fb40813d62e5c6c63f324 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 18:07:22 +0200 Subject: [PATCH 05/13] Add maxRecursionDepth in GenericEvaluationEngine --- .../GenericEvaluationEngine.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index ef55821..26f63d4 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -58,7 +58,7 @@ export class GenericEvaluator implements Evaluator { name: "Generic", priority: 1, }, - data: await this.constructObjectFromVariablesReference(reply.variablesReference, {}), + data: await this.constructObjectFromVariablesReference(reply.variablesReference), }, } } else { @@ -88,21 +88,24 @@ export class GenericEvaluator implements Evaluator { } } - private async constructObjectFromVariablesReference(variablesReference: number, knownObjects: { [ref: number]: any; }): Promise { + private async constructObjectFromVariablesReference( + variablesReference: number, + knownObjects: { [ref: number]: any; } = {}, + recursionDepth: number = 0, + maxRecursionDepth: number = 50, + ): Promise { var result: any = {}; knownObjects[variablesReference] = result; for (const variable of await this.session.getVariables({ variablesReference })) { let child: any; - if (variable.variablesReference > 0) { - if (variable.variablesReference in knownObjects) { - // If the object is known, we have a (potentially cyclic) reference - child = knownObjects[variable.variablesReference]; - } else { - // Recurse on a new object - child = await this.constructObjectFromVariablesReference(variable.variablesReference, knownObjects); - } + if (variable.variablesReference > 0 && variable.variablesReference in knownObjects) { + // If the object is known, we have a (potentially cyclic) reference + child = knownObjects[variable.variablesReference]; + } else if (variable.variablesReference > 0 && recursionDepth < maxRecursionDepth) { + // Recurse on a new object + child = await this.constructObjectFromVariablesReference(variable.variablesReference, knownObjects, recursionDepth + 1, maxRecursionDepth); } else { // If there are no childs, just assign the value child = variable.value; From 4aca50f67ae40ed340740ca06c1129fbf5b0310c Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 19:29:33 +0200 Subject: [PATCH 06/13] Use createGraph to represent object graphs constructed from variables --- demos/java/.project | 43 ++++++++++++------- .../GenericEvaluationEngine.ts | 29 ++++++++++++- 2 files changed, 55 insertions(+), 17 deletions(-) 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 26f63d4..5415991 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -1,4 +1,6 @@ import { + createGraph, + CreateGraphEdge, DataExtractionResult, DataExtractorId, } from "@hediet/debug-visualizer-data-extraction"; @@ -49,6 +51,8 @@ export class GenericEvaluator implements Evaluator { // Use structural information about variables // from the evaluation response if present. if (reply.variablesReference) { + let obj = await this.constructObjectFromVariablesReference(reply.variablesReference); + return { kind: "data", result: { @@ -58,7 +62,30 @@ export class GenericEvaluator implements Evaluator { name: "Generic", priority: 1, }, - data: await this.constructObjectFromVariablesReference(reply.variablesReference), + data: createGraph( + [obj], + (item: any) => { + function isObject(val: any): boolean { + return val && typeof val === "object"; + } + + const edges = new Array>(); + for (const [key, val] of Object.entries(item)) { + if (isObject(val)) { + edges.push({ label: key, to: val }); + } + } + + const label = `${item}`; + + return { + shape: "box", + edges, + color: item === obj ? "lightblue" : undefined, + label, + }; + } + ), }, } } else { From 7ab4e0c4b53d945b6ee8c641ec821d716d3a1100 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 19:40:58 +0200 Subject: [PATCH 07/13] Improve node labelling in generic evaluator --- .../EvaluationEngine/GenericEvaluationEngine.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index 5415991..4b30d32 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -40,7 +40,7 @@ export class GenericEvaluator implements Evaluator { expression, preferredExtractorId, }); - let reply; + let reply: { result: string, variablesReference: number }; try { reply = await this.session.evaluate({ expression: finalExpression, @@ -76,12 +76,13 @@ export class GenericEvaluator implements Evaluator { } } - const label = `${item}`; + const isTopLevel = item === obj; + const label = isObject(item) ? "object" : `${item}`; return { shape: "box", edges, - color: item === obj ? "lightblue" : undefined, + color: isTopLevel ? "lightblue" : undefined, label, }; } From e882593cea0957dbae8394674449a79606154a6d Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 19:44:13 +0200 Subject: [PATCH 08/13] Limit amount of scanned objects in generic evaluator --- .../EvaluationEngine/GenericEvaluationEngine.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index 4b30d32..980e199 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -120,18 +120,21 @@ export class GenericEvaluator implements Evaluator { variablesReference: number, knownObjects: { [ref: number]: any; } = {}, recursionDepth: number = 0, - maxRecursionDepth: number = 50, + maxRecursionDepth: number = 30, + maxKnownObjects: number = 100, ): Promise { var result: any = {}; knownObjects[variablesReference] = result; + const canRecurse = recursionDepth < maxRecursionDepth && Object.keys(knownObjects).length < maxKnownObjects; + for (const variable of await this.session.getVariables({ variablesReference })) { let child: any; if (variable.variablesReference > 0 && variable.variablesReference in knownObjects) { // If the object is known, we have a (potentially cyclic) reference child = knownObjects[variable.variablesReference]; - } else if (variable.variablesReference > 0 && recursionDepth < maxRecursionDepth) { + } else if (variable.variablesReference > 0 && canRecurse) { // Recurse on a new object child = await this.constructObjectFromVariablesReference(variable.variablesReference, knownObjects, recursionDepth + 1, maxRecursionDepth); } else { From d8840bf33bcdbf6298eb06f252d8ced6721d8208 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 20:22:10 +0200 Subject: [PATCH 09/13] Construct graph in generic evaluator explicitly --- .../GenericEvaluationEngine.ts | 85 +++++++++---------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index 980e199..575a198 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -3,6 +3,8 @@ import { CreateGraphEdge, DataExtractionResult, DataExtractorId, + GraphNode, + GraphVisualizationData, } from "@hediet/debug-visualizer-data-extraction"; import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession"; import { @@ -51,7 +53,12 @@ export class GenericEvaluator implements Evaluator { // Use structural information about variables // from the evaluation response if present. if (reply.variablesReference) { - let obj = await this.constructObjectFromVariablesReference(reply.variablesReference); + let graph: GraphVisualizationData = { + kind: { graph: true }, + nodes: [], + edges: [] + }; + await this.constructGraphFromVariablesReference(reply.result, reply.variablesReference, graph); return { kind: "data", @@ -62,31 +69,7 @@ export class GenericEvaluator implements Evaluator { name: "Generic", priority: 1, }, - data: createGraph( - [obj], - (item: any) => { - function isObject(val: any): boolean { - return val && typeof val === "object"; - } - - const edges = new Array>(); - for (const [key, val] of Object.entries(item)) { - if (isObject(val)) { - edges.push({ label: key, to: val }); - } - } - - const isTopLevel = item === obj; - const label = isObject(item) ? "object" : `${item}`; - - return { - shape: "box", - edges, - color: isTopLevel ? "lightblue" : undefined, - label, - }; - } - ), + data: graph, }, } } else { @@ -116,36 +99,48 @@ export class GenericEvaluator implements Evaluator { } } - private async constructObjectFromVariablesReference( + private async constructGraphFromVariablesReference( + label: string, variablesReference: number, - knownObjects: { [ref: number]: any; } = {}, + graph: GraphVisualizationData, + isTopLevel: boolean = true, + knownNodeIds: { [ref: number]: string; } = {}, recursionDepth: number = 0, maxRecursionDepth: number = 30, maxKnownObjects: number = 100, ): Promise { - var result: any = {}; - knownObjects[variablesReference] = result; + var result: GraphNode = { + id: `${variablesReference}`, + label, + color: isTopLevel ? "lightblue" : undefined, + shape: "box", + }; + knownNodeIds[variablesReference] = result.id; - const canRecurse = recursionDepth < maxRecursionDepth && Object.keys(knownObjects).length < maxKnownObjects; + const canRecurse = recursionDepth < maxRecursionDepth && Object.keys(knownNodeIds).length < maxKnownObjects; - for (const variable of await this.session.getVariables({ variablesReference })) { - let child: any; - - if (variable.variablesReference > 0 && variable.variablesReference in knownObjects) { + if (variablesReference > 0 && canRecurse) { + for (const variable of await this.session.getVariables({ variablesReference })) { // If the object is known, we have a (potentially cyclic) reference - child = knownObjects[variable.variablesReference]; - } else if (variable.variablesReference > 0 && canRecurse) { - // Recurse on a new object - child = await this.constructObjectFromVariablesReference(variable.variablesReference, knownObjects, recursionDepth + 1, maxRecursionDepth); - } else { - // If there are no childs, just assign the value - child = variable.value; - } + // Otherwise, recurse on the object. + if (!(variable.variablesReference in knownNodeIds)) { + await this.constructGraphFromVariablesReference( + variable.value, + variable.variablesReference, + graph, + false, // isTopLevel + knownNodeIds, + recursionDepth + 1, + maxRecursionDepth, + maxKnownObjects + ); + } - result[variable.name] = child; + graph.edges.push({ from: result.id, to: knownNodeIds[variable.variablesReference] }); + } } - return result; + graph.nodes.push(result); } protected getFinalExpression(args: { From 4a81e9910b34c935741b10bb2ba031c1baa121ac Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 20:36:05 +0200 Subject: [PATCH 10/13] Apply correct labels to graph edges in generic evaluator --- .../GenericEvaluationEngine.ts | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index 575a198..e5f6e22 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -107,24 +107,29 @@ export class GenericEvaluator implements Evaluator { knownNodeIds: { [ref: number]: string; } = {}, recursionDepth: number = 0, maxRecursionDepth: number = 30, - maxKnownObjects: number = 100, - ): Promise { - var result: GraphNode = { - id: `${variablesReference}`, + maxKnownNodes: number = 100, + ): Promise { + const hasChilds = variablesReference > 0; + const knownCount = Object.keys(knownNodeIds).length + 1; + const canRecurse = recursionDepth < maxRecursionDepth && knownCount < maxKnownNodes; + let result: GraphNode = { + id: hasChilds ? `${variablesReference}` : `__${label}@${knownCount}__`, label, color: isTopLevel ? "lightblue" : undefined, shape: "box", }; knownNodeIds[variablesReference] = result.id; - const canRecurse = recursionDepth < maxRecursionDepth && Object.keys(knownNodeIds).length < maxKnownObjects; - - if (variablesReference > 0 && canRecurse) { + if (hasChilds && canRecurse) { for (const variable of await this.session.getVariables({ variablesReference })) { - // If the object is known, we have a (potentially cyclic) reference - // Otherwise, recurse on the object. - if (!(variable.variablesReference in knownNodeIds)) { - await this.constructGraphFromVariablesReference( + let childId: string; + + if (variable.variablesReference > 0 && variable.variablesReference in knownNodeIds) { + // If the object is known, we have a (potentially cyclic) reference. + childId = knownNodeIds[variable.variablesReference]; + } else { + // Otherwise recurse + childId = await this.constructGraphFromVariablesReference( variable.value, variable.variablesReference, graph, @@ -132,15 +137,16 @@ export class GenericEvaluator implements Evaluator { knownNodeIds, recursionDepth + 1, maxRecursionDepth, - maxKnownObjects + maxKnownNodes ); } - graph.edges.push({ from: result.id, to: knownNodeIds[variable.variablesReference] }); + graph.edges.push({ from: result.id, to: childId, label: variable.name }); } } graph.nodes.push(result); + return result.id; } protected getFinalExpression(args: { From cc272acb061ead1304c748a7a19fe2909ca6df12 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 22:04:48 +0200 Subject: [PATCH 11/13] Remove redundant imports in GenericEvaluationEngine --- .../EvaluationEngine/GenericEvaluationEngine.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index e5f6e22..6653466 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -1,6 +1,4 @@ import { - createGraph, - CreateGraphEdge, DataExtractionResult, DataExtractorId, GraphNode, From ee9f40503330830e40a98b901db3221ebd77bb35 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 2 Sep 2020 22:41:55 +0200 Subject: [PATCH 12/13] Use breadth-first-search in GenericEvaluationEngine A BFS generates a better graph if the graph is very deep since a DFS would get stuck in the first branch (if the limit is low). --- .../GenericEvaluationEngine.ts | 118 +++++++++++------- 1 file changed, 72 insertions(+), 46 deletions(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index 6653466..bd42198 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -51,12 +51,7 @@ export class GenericEvaluator implements Evaluator { // Use structural information about variables // from the evaluation response if present. if (reply.variablesReference) { - let graph: GraphVisualizationData = { - kind: { graph: true }, - nodes: [], - edges: [] - }; - await this.constructGraphFromVariablesReference(reply.result, reply.variablesReference, graph); + const graph = await this.constructGraphFromVariablesReference(reply.result, reply.variablesReference); return { kind: "data", @@ -97,54 +92,85 @@ export class GenericEvaluator implements Evaluator { } } + /** + * 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( - label: string, - variablesReference: number, - graph: GraphVisualizationData, - isTopLevel: boolean = true, - knownNodeIds: { [ref: number]: string; } = {}, - recursionDepth: number = 0, - maxRecursionDepth: number = 30, + rootLabel: string, + rootVariablesReference: number, + maxDepth: number = 30, maxKnownNodes: number = 100, - ): Promise { - const hasChilds = variablesReference > 0; - const knownCount = Object.keys(knownNodeIds).length + 1; - const canRecurse = recursionDepth < maxRecursionDepth && knownCount < maxKnownNodes; - let result: GraphNode = { - id: hasChilds ? `${variablesReference}` : `__${label}@${knownCount}__`, - label, - color: isTopLevel ? "lightblue" : undefined, - shape: "box", + ): Promise { + // Perform a breadth-first search on the object to construct the graph + + const graph: GraphVisualizationData = { + kind: { graph: true }, + nodes: [], + edges: [] }; - knownNodeIds[variablesReference] = result.id; + 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, + }]; - if (hasChilds && canRecurse) { - for (const variable of await this.session.getVariables({ variablesReference })) { - let childId: string; + let knownCount: number = 0; - if (variable.variablesReference > 0 && variable.variablesReference in knownNodeIds) { - // If the object is known, we have a (potentially cyclic) reference. - childId = knownNodeIds[variable.variablesReference]; - } else { - // Otherwise recurse - childId = await this.constructGraphFromVariablesReference( - variable.value, - variable.variablesReference, - graph, - false, // isTopLevel - knownNodeIds, - recursionDepth + 1, - maxRecursionDepth, - maxKnownNodes - ); + 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 }); + } } - graph.edges.push({ from: result.id, to: childId, label: variable.name }); - } - } + nodeId = node.id; + } else { + // The variable is a visited object (e.g. due to a cyclic reference) - graph.nodes.push(result); - return result.id; + 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: { From b47efb7176460185c65e29344aa0fe2f3f379088 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 00:37:07 +0200 Subject: [PATCH 13/13] Reduce default node limit in GenericEvaluator to 50 --- .../EvaluationEngine/GenericEvaluationEngine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts index bd42198..126b1f0 100644 --- a/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts +++ b/extension/src/EvaluationWatchService/EvaluationEngine/GenericEvaluationEngine.ts @@ -108,7 +108,7 @@ export class GenericEvaluator implements Evaluator { rootLabel: string, rootVariablesReference: number, maxDepth: number = 30, - maxKnownNodes: number = 100, + maxKnownNodes: number = 50, ): Promise { // Perform a breadth-first search on the object to construct the graph