From c14c135bc2f9a546efa5aaba98d05715043b15c5 Mon Sep 17 00:00:00 2001 From: snh21 Date: Sun, 13 Jul 2025 15:10:55 +0200 Subject: [PATCH 1/4] feat(cfg): detect loop over empty vector as dead - #1748 --- src/control-flow/cfg-dead-code.ts | 19 ++++++++++ src/dataflow/environments/built-in.ts | 2 +- src/dataflow/eval/resolve/alias-tracking.ts | 1 - src/dataflow/eval/resolve/resolve.ts | 4 +- .../dead-code/cfg-dead-code.test.ts | 37 +++++++++++++++++++ 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/control-flow/cfg-dead-code.ts b/src/control-flow/cfg-dead-code.ts index 1704eb7a395..c50f4796843 100644 --- a/src/control-flow/cfg-dead-code.ts +++ b/src/control-flow/cfg-dead-code.ts @@ -125,6 +125,25 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { this.handleWithCondition(data); } + protected onForLoopCall(data: { call: DataflowGraphVertexFunctionCall; variable: FunctionArgument; vector: FunctionArgument; body: FunctionArgument; }): void { + if(data.vector === EmptyArgument || data.body === EmptyArgument) { + return; + } + + const values = valueSetGuard(resolveIdToValue(data.vector.nodeId, { graph: this.config.dfg, environment: data.call.environment, full: true, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables })); + if(values === undefined) { + return; + } + + const isEmptyVector = + values.elements.length === 1 && + values.elements[0].type === 'vector' && + isValue(values.elements[0].elements) && + values.elements[0].elements.length === 0; + + this.cachedStatements.set(data.body.nodeId, isEmptyVector); + } + protected onDefaultFunctionCall(data: { call: DataflowGraphVertexFunctionCall; }): void { this.handleFunctionCall(data); } diff --git a/src/dataflow/environments/built-in.ts b/src/dataflow/environments/built-in.ts index d7c9be1832a..9e0cf9403a9 100644 --- a/src/dataflow/environments/built-in.ts +++ b/src/dataflow/environments/built-in.ts @@ -96,7 +96,7 @@ export interface DefaultBuiltInProcessorConfiguration extends ForceArguments { } -export type BuiltInEvalHandler = (resolve: VariableResolve, a: RNodeWithParent, env: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap) => Value; +export type BuiltInEvalHandler = (resolve: VariableResolve, a: RNodeWithParent, env?: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap) => Value; function defaultBuiltInProcessor( name: RSymbol, diff --git a/src/dataflow/eval/resolve/alias-tracking.ts b/src/dataflow/eval/resolve/alias-tracking.ts index 52649828e90..76a96a9e081 100644 --- a/src/dataflow/eval/resolve/alias-tracking.ts +++ b/src/dataflow/eval/resolve/alias-tracking.ts @@ -353,7 +353,6 @@ export function trackAliasesInGraph(id: NodeId, graph: DataflowGraph, idMap?: As const isFn = vertex.tag === VertexType.FunctionCall; - // travel all read and defined-by edges for(const [targetId, edge] of outgoingEdges) { if(isFn) { diff --git a/src/dataflow/eval/resolve/resolve.ts b/src/dataflow/eval/resolve/resolve.ts index edebaa94e4e..8e0c39ed080 100644 --- a/src/dataflow/eval/resolve/resolve.ts +++ b/src/dataflow/eval/resolve/resolve.ts @@ -36,7 +36,7 @@ export function resolveNode(resolve: VariableResolve, a: RNodeWithParent, env?: return intervalFrom(a.content.num, a.content.num); } else if(a.type === RType.Logical) { return a.content.valueOf() ? ValueLogicalTrue : ValueLogicalFalse; - } else if(a.type === RType.FunctionCall && env && graph) { + } else if(a.type === RType.FunctionCall && graph) { const origin = getOriginInDfg(graph, a.info.id)?.[0]; if(origin === undefined || origin.type !== OriginType.BuiltInFunctionOrigin) { return Top; @@ -66,7 +66,7 @@ export function resolveNode(resolve: VariableResolve, a: RNodeWithParent, env?: * @param map - Idmap of Dataflow Graph * @returns ValueVector or Top */ -export function resolveAsVector(resolve: VariableResolve, a: RNodeWithParent, env: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap): Value { +export function resolveAsVector(resolve: VariableResolve, a: RNodeWithParent, env?: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap): Value { guard(a.type === RType.FunctionCall); const values: Value[] = []; diff --git a/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts b/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts index 19cd3d37394..6c976f2c792 100644 --- a/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts +++ b/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts @@ -31,7 +31,44 @@ describe('Control Flow Graph', withTreeSitter(parser => { }); } + describe('Dead Code Removal', () => { + describe('Empty Vector', () => { + assertDeadCode('x<-1\nfor (i in c())\n{ print(i) }', { reachableFromStart: ['1@x'], unreachableFromStart: ['2@i'] }); + assertDeadCode('x<-1; y <- c()\nfor (i in y)\n{ print(i) }', { reachableFromStart: ['1@x'], unreachableFromStart: ['2@i'] }); + // c <- function() 1:10 + // ------ + // f <- function(p = c()) { for(i in p) { x <- 2} } --> not dead + // one more indirection + // detect loop that is not dead + // nested + + /** + * f <- function() { + * for(i in c()) {} + * function() i <<- 42 + * } + * + * g <- f() + * g() + * + * > i + * Error: object 'i' not found + * > for(i in c()) {} + * > i + * NULL + */ + + /// ------------------------------------ + // doesItLoopyOnlyOncey(<...>) + // for(i in c(1)) { print(42) } + // while(TRUE) { print(2); [break|stopifnot()]; } + // for(i in 1:10) { print(42); break } + // for(i in 1:10) { print(42); if(u) break else break } + // x <- 2; while(TRUE) { x <- x + 1; break } + // a:b (1:9, 9:1) | seq | seq_along | + }); + describe.each([ { prefix: 'if(TRUE)', swap: false }, { prefix: 'if(FALSE)', swap: true }, From badb1ddcf624d362ff23da585be8365fb91b46f7 Mon Sep 17 00:00:00 2001 From: snh21 Date: Mon, 14 Jul 2025 19:36:04 +0200 Subject: [PATCH 2/4] feat-fix(cfg-dead-code): only remove body of for loop - #1748 --- src/control-flow/cfg-dead-code.ts | 8 ++++++-- .../control-flow/dead-code/cfg-dead-code.test.ts | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/control-flow/cfg-dead-code.ts b/src/control-flow/cfg-dead-code.ts index c50f4796843..5698f3504c4 100644 --- a/src/control-flow/cfg-dead-code.ts +++ b/src/control-flow/cfg-dead-code.ts @@ -1,5 +1,5 @@ /* currently this does not do work on function definitions */ -import type { ControlFlowInformation } from './control-flow-graph'; +import type { CfgMidMarkerVertex, ControlFlowInformation } from './control-flow-graph'; import { CfgEdgeType } from './control-flow-graph'; import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id'; import { Ternary } from '../util/logic'; @@ -141,7 +141,11 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { isValue(values.elements[0].elements) && values.elements[0].elements.length === 0; - this.cachedStatements.set(data.body.nodeId, isEmptyVector); + this.cachedConditions.set(data.call.id, isEmptyVector ? Ternary.Never : Ternary.Always); + } + + protected onMidMarkerNode(_node: CfgMidMarkerVertex): void { + } protected onDefaultFunctionCall(data: { call: DataflowGraphVertexFunctionCall; }): void { diff --git a/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts b/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts index 6c976f2c792..356afc8e247 100644 --- a/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts +++ b/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts @@ -34,8 +34,8 @@ describe('Control Flow Graph', withTreeSitter(parser => { describe('Dead Code Removal', () => { describe('Empty Vector', () => { - assertDeadCode('x<-1\nfor (i in c())\n{ print(i) }', { reachableFromStart: ['1@x'], unreachableFromStart: ['2@i'] }); - assertDeadCode('x<-1; y <- c()\nfor (i in y)\n{ print(i) }', { reachableFromStart: ['1@x'], unreachableFromStart: ['2@i'] }); + assertDeadCode('x<-1\nfor (i in c())\n{ print(i) }', { reachableFromStart: ['1@x', '2@i'], unreachableFromStart: ['3@i'] }); + assertDeadCode('x<-1; y <- c()\nfor (i in y)\n{ print(i) }', { reachableFromStart: ['1@x', '2@i'], unreachableFromStart: ['3@i'] }); // c <- function() 1:10 // ------ // f <- function(p = c()) { for(i in p) { x <- 2} } --> not dead From 2a6d3604017963830da6308910a16043fa68eaa9 Mon Sep 17 00:00:00 2001 From: snh21 Date: Wed, 6 Aug 2025 16:57:42 +0200 Subject: [PATCH 3/4] feat(alias-tracking): improve tracking by graph - #1748 --- src/control-flow/cfg-dead-code.ts | 6 +- src/dataflow/eval/resolve/alias-tracking.ts | 70 ++++- src/dataflow/eval/values/general.ts | 20 -- .../dead-code/cfg-dead-code.test.ts | 34 +-- .../dataflow/environments/resolve.test.ts | 275 +++++++++--------- .../query/resolve-value-query.test.ts | 2 + 6 files changed, 205 insertions(+), 202 deletions(-) diff --git a/src/control-flow/cfg-dead-code.ts b/src/control-flow/cfg-dead-code.ts index 5698f3504c4..49d754dbae6 100644 --- a/src/control-flow/cfg-dead-code.ts +++ b/src/control-flow/cfg-dead-code.ts @@ -143,11 +143,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { this.cachedConditions.set(data.call.id, isEmptyVector ? Ternary.Never : Ternary.Always); } - - protected onMidMarkerNode(_node: CfgMidMarkerVertex): void { - - } - + protected onDefaultFunctionCall(data: { call: DataflowGraphVertexFunctionCall; }): void { this.handleFunctionCall(data); } diff --git a/src/dataflow/eval/resolve/alias-tracking.ts b/src/dataflow/eval/resolve/alias-tracking.ts index 76a96a9e081..582bc26122f 100644 --- a/src/dataflow/eval/resolve/alias-tracking.ts +++ b/src/dataflow/eval/resolve/alias-tracking.ts @@ -7,7 +7,7 @@ import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; import { envFingerprint } from '../../../slicing/static/fingerprint'; import { VisitingQueue } from '../../../slicing/static/visiting-queue'; import { guard } from '../../../util/assert'; -import type { BuiltInIdentifierConstant } from '../../environments/built-in'; +import { BuiltInEvalHandlerMapper, BuiltInMappingName, BuiltInProcessorMapper, type BuiltInIdentifierConstant } from '../../environments/built-in'; import type { REnvironmentInformation } from '../../environments/environment'; import { initializeCleanEnvironments } from '../../environments/environment'; import type { Identifier } from '../../environments/identifier'; @@ -19,7 +19,8 @@ import type { ReplacementOperatorHandlerArgs } from '../../graph/unknown-replace import { onReplacementOperator } from '../../graph/unknown-replacement'; import { onUnknownSideEffect } from '../../graph/unknown-side-effect'; import { VertexType } from '../../graph/vertex'; -import { valueFromRNodeConstant, valueFromTsValue } from '../values/general'; +import { getOriginInDfg, OriginType } from '../../origin/dfg-get-origin'; +import { valueFromTsValue } from '../values/general'; import type { Lift, Value, ValueSet } from '../values/r-value'; import { Bottom, isTop, Top } from '../values/r-value'; import { setFrom } from '../values/sets/set-constants'; @@ -160,20 +161,20 @@ export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { env switch(node.type) { case RType.Argument: + return resolveIdToValue(node.value, { environment, graph, idMap, full, resolve }); case RType.Symbol: if(environment) { return full ? trackAliasInEnvironments(resolve, node.lexeme, environment, graph, idMap) : Top; } else if(graph && resolve === VariableResolve.Alias) { - return full ? trackAliasesInGraph(node.info.id, graph, idMap) : Top; + return full ? trackAliasesInGraph(resolve, node.info.id, graph, idMap) : Top; } else { return Top; } case RType.FunctionCall: - return setFrom(resolveNode(resolve, node, environment, graph, idMap)); case RType.String: case RType.Number: case RType.Logical: - return setFrom(valueFromRNodeConstant(node)); + return setFrom(resolveNode(resolve, node, environment, graph, idMap)); default: return Top; } @@ -295,6 +296,11 @@ function isNestedInLoop(node: RNodeWithParent | undefined, ast: AstIdMap): boole return isNestedInLoop(parentNode, ast); } +/** + * We currently do not support these functions and have to stop the analysis + */ +const unsupportedFunctions = new Set(['builtin:replacement']); + /** * Please use {@link resolveIdToValue} * @@ -305,11 +311,20 @@ function isNestedInLoop(node: RNodeWithParent | undefined, ast: AstIdMap): boole * @param idMap - idmap of dataflow graph * @returns Value of node or Top/Bottom */ -export function trackAliasesInGraph(id: NodeId, graph: DataflowGraph, idMap?: AstIdMap): ResolveResult { +export function trackAliasesInGraph(resolve: VariableResolve, id: NodeId, graph: DataflowGraph, idMap?: AstIdMap): ResolveResult { + // Give up if the graph contains unlinked side effects + for(const se of graph.unknownSideEffects.values()) { + if(typeof se === 'object') { + continue; + } + + return Top; + } + idMap ??= graph.idMap; guard(idMap !== undefined, 'The ID map is required to get the lineage of a node'); const start = graph.getVertex(id); - guard(start !== undefined, 'Unable to find start for alias tracking'); + guard(start !== undefined, 'Unable to find start for alias tracking ' + id); const queue = new VisitingQueue(25); const clean = initializeCleanEnvironments(); @@ -352,29 +367,60 @@ export function trackAliasesInGraph(id: NodeId, graph: DataflowGraph, idMap?: As } const isFn = vertex.tag === VertexType.FunctionCall; + if(isFn && vertex.origin !== 'unnamed') { + if(unsupportedFunctions.has(vertex.origin[0] as BuiltInMappingName)) { + return Top; + } + + // Try with processor to avoid calling getOriginInDfg as resolveNode will call that as well + if(BuiltInEvalHandlerMapper[vertex.origin[0] as keyof typeof BuiltInEvalHandlerMapper] !== undefined) { + resultIds.push(vertex.id); + continue; + } + + // Try with getOrigin in case of aliasing + const origin = getOriginInDfg(graph, vertex.id); + if(origin !== undefined + && origin.length > 0 + && origin[0].type === OriginType.BuiltInFunctionOrigin + && BuiltInEvalHandlerMapper[origin[0].proc as keyof typeof BuiltInEvalHandlerMapper] !== undefined) { + resultIds.push(vertex.id); + continue; + } + } // travel all read and defined-by edges for(const [targetId, edge] of outgoingEdges) { if(isFn) { - if(edge.types === EdgeType.Returns || edge.types === EdgeType.DefinedByOnCall || edge.types === EdgeType.DefinedBy) { + const interestingFnEdges = EdgeType.Returns | EdgeType.DefinedBy | EdgeType.DefinedByOnCall; + if((edge.types & ~interestingFnEdges) === 0 && edge.types !== 0) { queue.add(targetId, baseEnvironment, cleanFingerprint, false); } continue; } - // currently, they have to be exact! - if(edge.types === EdgeType.Reads || edge.types === EdgeType.DefinedBy || edge.types === EdgeType.DefinedByOnCall) { + + const interestingEdges = EdgeType.Reads | EdgeType.DefinedBy | EdgeType.DefinedByOnCall; + if((edge.types & ~interestingEdges) === 0 && edge.types !== 0) { queue.add(targetId, baseEnvironment, cleanFingerprint, false); } } } - if(forceBot || resultIds.length === 0) { + + if(forceBot) { return Bottom; + } + + if(resultIds.length === 0) { + return Top; } + + resultIds.sort((a,b) => String(a).localeCompare(String(b))); + const values: Set = new Set(); for(const id of resultIds) { const node = idMap.get(id); if(node !== undefined) { - values.add(valueFromRNodeConstant(node)); + values.add(resolveNode(resolve, node, undefined, graph, graph.idMap)); } } return setFrom(...values); diff --git a/src/dataflow/eval/values/general.ts b/src/dataflow/eval/values/general.ts index e2f500a9691..d041c6dfd8e 100644 --- a/src/dataflow/eval/values/general.ts +++ b/src/dataflow/eval/values/general.ts @@ -1,5 +1,3 @@ -import type { RNodeWithParent } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; import { intervalFrom } from './intervals/interval-constants'; import { ValueLogicalFalse, ValueLogicalTrue } from './logical/logical-constants'; import type { Lift, Value, ValueSet } from './r-value'; @@ -48,21 +46,3 @@ export function valueFromTsValue(a: unknown): Value { return Top; } - - -/** - * Converts a constant from an RNode into an abstract value - * @param a - RNode constant - * @returns abstract value - */ -export function valueFromRNodeConstant(a: RNodeWithParent): Value { - if(a.type === RType.String) { - return stringFrom(a.content.str); - } else if(a.type === RType.Number) { - return intervalFrom(a.content.num, a.content.num); - } else if(a.type === RType.Logical) { - return a.content.valueOf() ? ValueLogicalTrue : ValueLogicalFalse; - } - - return Top; -} \ No newline at end of file diff --git a/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts b/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts index 356afc8e247..a45f4fe06a9 100644 --- a/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts +++ b/test/functionality/control-flow/dead-code/cfg-dead-code.test.ts @@ -36,37 +36,11 @@ describe('Control Flow Graph', withTreeSitter(parser => { describe('Empty Vector', () => { assertDeadCode('x<-1\nfor (i in c())\n{ print(i) }', { reachableFromStart: ['1@x', '2@i'], unreachableFromStart: ['3@i'] }); assertDeadCode('x<-1; y <- c()\nfor (i in y)\n{ print(i) }', { reachableFromStart: ['1@x', '2@i'], unreachableFromStart: ['3@i'] }); - // c <- function() 1:10 - // ------ - // f <- function(p = c()) { for(i in p) { x <- 2} } --> not dead - // one more indirection - // detect loop that is not dead - // nested - /** - * f <- function() { - * for(i in c()) {} - * function() i <<- 42 - * } - * - * g <- f() - * g() - * - * > i - * Error: object 'i' not found - * > for(i in c()) {} - * > i - * NULL - */ - - /// ------------------------------------ - // doesItLoopyOnlyOncey(<...>) - // for(i in c(1)) { print(42) } - // while(TRUE) { print(2); [break|stopifnot()]; } - // for(i in 1:10) { print(42); break } - // for(i in 1:10) { print(42); if(u) break else break } - // x <- 2; while(TRUE) { x <- x + 1; break } - // a:b (1:9, 9:1) | seq | seq_along | + // Negative Test / Don't remove useful loop + assertDeadCode('for (i in c(1, 2))\n{ print(i) }', { reachableFromStart: ['1@i', '2@i'], unreachableFromStart: [] }); + assertDeadCode('c <- function() 1:10 \n for (i in c())\n{ print(i) }', { reachableFromStart: ['2@i', '3@i'], unreachableFromStart: [] }); + assertDeadCode('f <- function(p = c()) { for(i in p) { x <- 2} }', { reachableFromStart: ['1@i', '1@x'], unreachableFromStart: [] }); }); describe.each([ diff --git a/test/functionality/dataflow/environments/resolve.test.ts b/test/functionality/dataflow/environments/resolve.test.ts index 11eedd0c141..9187696ceb8 100644 --- a/test/functionality/dataflow/environments/resolve.test.ts +++ b/test/functionality/dataflow/environments/resolve.test.ts @@ -9,9 +9,8 @@ import { valueFromTsValue } from '../../../../src/dataflow/eval/values/general'; import { setFrom } from '../../../../src/dataflow/eval/values/sets/set-constants'; import type { Lift, Value } from '../../../../src/dataflow/eval/values/r-value'; import { Bottom, isBottom, isTop, Top } from '../../../../src/dataflow/eval/values/r-value'; -import { withShell } from '../../_helper/shell'; -import { PipelineExecutor } from '../../../../src/core/pipeline-executor'; -import { DEFAULT_DATAFLOW_PIPELINE } from '../../../../src/core/steps/pipeline/default-pipelines'; +import { withTreeSitter } from '../../_helper/shell'; +import { createDataflowPipeline } from '../../../../src/core/steps/pipeline/default-pipelines'; import { requestFromInput } from '../../../../src/r-bridge/retriever'; import { type SingleSlicingCriterion, slicingCriterionToId } from '../../../../src/slicing/criterion/parse'; import { intervalFromValues } from '../../../../src/dataflow/eval/values/intervals/interval-constants'; @@ -26,170 +25,176 @@ enum Allow { Bottom = 2 }; -describe.sequential('Resolve', withShell(shell => { - function set(values: unknown[]) { - return setFrom(...values.map(v => valueFromTsValue(v))); - } +describe('Resolve', withTreeSitter(shell => { + describe.each(['env', 'graph'])('resolve with %s', (resolveWith) => { - function interval(start: Lift, end: Lift = start, startInclusive = true, endInclusive = true) { - return intervalFromValues( - typeof start === 'number' ? getScalarFromInteger(start) : start, - typeof end === 'number' ? getScalarFromInteger(end) : end, - startInclusive, - endInclusive - ); - } + function set(values: unknown[]) { + return setFrom(...values.map(v => valueFromTsValue(v))); + } - function vector(values: unknown[]) { - return setFrom(vectorFrom(values.map(v => valueFromTsValue(v)))); - } + function interval(start: Lift, end: Lift = start, startInclusive = true, endInclusive = true) { + return intervalFromValues( + typeof start === 'number' ? getScalarFromInteger(start) : start, + typeof end === 'number' ? getScalarFromInteger(end) : end, + startInclusive, + endInclusive + ); + } - function testResolve( - name: string, - identifier: SingleSlicingCriterion, - code: string, - expectedValues: Value, - allow: Allow = Allow.None - ): void { - const effectiveName = decorateLabelContext(label(name), ['resolve']); + function vector(values: unknown[]) { + return setFrom(vectorFrom(values.map(v => valueFromTsValue(v)))); + } - test(effectiveName, async() => { - const dataflow = await new PipelineExecutor(DEFAULT_DATAFLOW_PIPELINE, { - parser: shell, - request: requestFromInput(code.trim()), - }, defaultConfigOptions).allRemainingSteps(); + function testResolve( + name: string, + identifier: SingleSlicingCriterion, + code: string, + expectedValues: Value, + allow: Allow = Allow.None + ): void { + const effectiveName = decorateLabelContext(label(name), ['resolve']); - const resolved = resolveIdToValue(slicingCriterionToId(identifier, dataflow.normalize.idMap), { - environment: dataflow.dataflow.environment, - graph: dataflow.dataflow.graph, - idMap: dataflow.normalize.idMap, - full: true, - resolve: defaultConfigOptions.solver.variables - }); - - if((allow & Allow.Top) == Allow.Top && isTop(resolved)) { - return; - } + test(effectiveName, async() => { + const { dataflow, normalize } = await createDataflowPipeline(shell, { request: requestFromInput(code.trim()) }, defaultConfigOptions).allRemainingSteps(); - if((allow & Allow.Bottom) == Allow.Bottom && isBottom(resolved)) { - return; - } + const resolved = resolveIdToValue(slicingCriterionToId(identifier, normalize.idMap), { + environment: resolveWith === 'env' ? dataflow.environment : undefined, + graph: dataflow.graph, + idMap: normalize.idMap, + full: true, + resolve: defaultConfigOptions.solver.variables + }); + + if((allow & Allow.Top) == Allow.Top && isTop(resolved)) { + return; + } - assert.deepEqual(resolved, expectedValues, `Resolved Value does not match expected Value. Code was: ${code}`); - }); - } + if((allow & Allow.Bottom) == Allow.Bottom && isBottom(resolved)) { + return; + } - function testMutate(name: string, line: number, identifier: string, code: string, expected: Value, allow: Allow = Allow.None) { - const distractors: string[] = [ - `while(FALSE) { ${identifier} <- 0 }`, - `if(FALSE) { ${identifier} <- 0 }`, - 'u <- u + 1', - `if(FALSE) { rm(${identifier})}` - ]; + assert.deepEqual(resolved, expectedValues, `Resolved Value does not match expected Value. Code was: ${code}`); + }); + } - describe(name, () => { - for(const distractor of distractors) { - const mutatedCode = code.split('\n').map(line => `${distractor}\n${line}`).join('\n'); - testResolve(distractor, `${line*2}@${identifier}`, mutatedCode, expected, allow); - } - }); - } + function testMutate(name: string, line: number, identifier: string, code: string, expected: Value, allow: Allow = Allow.None) { + const distractors: string[] = [ + `while(FALSE) { ${identifier} <- 0 }`, + `if(FALSE) { ${identifier} <- 0 }`, + 'u <- u + 1', + `if(FALSE) { rm(${identifier})}` + ]; + + describe(name, () => { + for(const distractor of distractors) { + const mutatedCode = code.split('\n').map(line => `${distractor}\n${line}`).join('\n'); + testResolve(distractor, `${line*2}@${identifier}`, mutatedCode, expected, allow); + } + }); + } - describe('Negative Tests', () => { - testResolve('Unknown if', '2@x', 'if(u) { x <- 2 } else { x <- foo() } \n x', Top); + describe('Negative Tests', () => { + testResolve('Unknown if', '2@x', 'if(u) { x <- 2 } else { x <- foo() } \n x', Top); - testResolve('Unknown Fn', '2@x', 'x <- foo(1) \n x', Top); - testResolve('Unknown Fn 2', '2@f', 'f <- function(x = 3) { foo(x) } \n f()', Top); - testResolve('Recursion', '2@f', 'f <- function(x = 3) { f(x) } \n f()', Top); - testResolve('Get Unknown', '3@x', 'y <- 5 \n x <- get(u) \n x', Top); + testResolve('Unknown Fn', '2@x', 'x <- foo(1) \n x', Top); + testResolve('Unknown Fn 2', '2@f', 'f <- function(x = 3) { foo(x) } \n f()', Top); + testResolve('Recursion', '2@f', 'f <- function(x = 3) { f(x) } \n f()', Top); + testResolve('Get Unknown', '3@x', 'y <- 5 \n x <- get(u) \n x', Top); - testResolve('rm()', '3@x', 'x <- 1 \n rm(x) \n x', Bottom, Allow.Top); + testResolve('rm()', '3@x', 'x <- 1 \n rm(x) \n x', Bottom, Allow.Top); - testResolve('Eval before Variable', '3@x', 'x <- 1 \n eval(u) \n x', Top); - }); + testResolve('Eval before Variable', '3@x', 'x <- 1 \n eval(u) \n x', Top); + }); - describe('Resolve Value', () => { - testResolve('Constant Value', '1@x', 'x <- 5', set([5])); - testResolve('Constant Value Str', '1@x', 'x <- "foo"', set(['foo'])); - testResolve('Alias Constant Value', '3@x', 'y <- 5 \n x <- y \n x', set([5])); + describe('Resolve Value', () => { + testResolve('Constant Value', '1@x', 'x <- 5', set([5])); + testResolve('Constant Value Str', '1@x', 'x <- "foo"', set(['foo'])); + testResolve('Alias Constant Value', '3@x', 'y <- 5 \n x <- y \n x', set([5])); - testResolve('rm() with alias', '4@x', 'y <- 2 \n x <- y \n rm(y) \n x', set([2])); + testResolve('rm() with alias', '4@x', 'y <- 2 \n x <- y \n rm(y) \n x', set([2])); - // Not yet supported - testResolve('Fn Default Arg', '2@f', 'f <- function(x = 3) { x } \n f()', set([3]), Allow.Top); - testResolve('Get', '3@x', 'y <- 5 \n x <- get("y") \n x', set([5]), Allow.Top); - testResolve('Super Assign', '4@x', 'x <- 1 \n f <- function() { x <<- 2} \n f() \n x', set([2]), Allow.Top); - testResolve('Plus One', '3@x', 'x <- 1 \n x <- x+1 \n x', interval(1, Top), Allow.Top); + // Not yet supported + testResolve('Fn Default Arg', '2@f', 'f <- function(x = 3) { x } \n f()', set([3]), Allow.Top); + testResolve('Get', '3@x', 'y <- 5 \n x <- get("y") \n x', set([5]), Allow.Top); + testResolve('Super Assign', '4@x', 'x <- 1 \n f <- function() { x <<- 2} \n f() \n x', set([2]), Allow.Top); + testResolve('Plus One', '3@x', 'x <- 1 \n x <- x+1 \n x', interval(1, Top), Allow.Top); - testResolve('Random Loop', '4@x', 'x <- 1 \n while(TRUE) { x <- x + 1 \n if(runif(1) > 0.5) { break } } \n x', Top); - testResolve('Loop plus one', '4@i', 'for(i in 1:10) { i \n i <- i + 1 \n i} \n i', interval(2, 11), Allow.Top); - testResolve('Loop plus x', '5@x', 'x <- 2 \n for(i in 1:10) { x \n x <- i + x \n i} \n x', interval(2, 57), Allow.Top); + testResolve('Random Loop', '4@x', 'x <- 1 \n while(TRUE) { x <- x + 1 \n if(runif(1) > 0.5) { break } } \n x', Top); + testResolve('Loop plus one', '4@i', 'for(i in 1:10) { i \n i <- i + 1 \n i} \n i', interval(2, 11), Allow.Top); + testResolve('Loop plus x', '5@x', 'x <- 2 \n for(i in 1:10) { x \n x <- i + x \n i} \n x', interval(2, 57), Allow.Top); - testResolve('Superassign Arith', '5@x', 'y <- 4 \n x <- 1 \n f <- function() { x <<- 2 * y } \n f() \n x', interval(8), Allow.Top); - }); + testResolve('Superassign Arith', '5@x', 'y <- 4 \n x <- 1 \n f <- function() { x <<- 2 * y } \n f() \n x', interval(8), Allow.Top); + }); - describe('Resolve Value (distractors)', () => { - testMutate('Constant Value', 1, 'x', 'x <- 5', set([5])); - testMutate('Constant Value branch', 4, 'x', 'if(u) { \n x <- 5} else { \n x <- 6 } \n x', set([5, 6])); - testMutate('Alias Constant Value', 3, 'x', 'y <- 5 \n x <- y \n x', set([5])); - testMutate('Vector', 2, 'x', 'x <- 1 \n x <- c(1,2,3)', vector([1,2,3])); + describe('Resolve Value (distractors)', () => { + testMutate('Constant Value', 1, 'x', 'x <- 5', set([5])); + testMutate('Constant Value branch', 4, 'x', 'if(u) { \n x <- 5} else { \n x <- 6 } \n x', set([5, 6])); + testMutate('Alias Constant Value', 3, 'x', 'y <- 5 \n x <- y \n x', set([5])); + testMutate('Vector', 2, 'x', 'x <- 1 \n x <- c(1,2,3)', vector([1,2,3])); - }); + }); - describe('Resolve (vectors)', () => { + describe('Resolve (vectors)', () => { // Do not resolve vector, if c is redefined - testResolve('c redefined', '2@x', 'c <- function() {} \n x <- c(1,2,3)', Top); + testResolve('c redefined', '2@x', 'c <- function() {} \n x <- c(1,2,3)', Top); - testResolve('Simple Vector (int)', '2@x', 'x <- c(1, 2, 3, 4) \n x', vector([1, 2, 3, 4])); - testResolve('Simple Vector (string)', '2@x', 'x <- c("a", "b", "c", "d") \n x', vector(['a', 'b', 'c', 'd'])); - testResolve('Vector with alias', '2@x', 'y <- 1 \n x <- c(y,2)', vector([1, 2])); - testResolve('Vector in vector', '1@x', 'x <- c(1, 2, c(3, 4, 5))', vector([1, 2, 3, 4, 5])); - testResolve('Vector in vector alias', '2@x', 'y <- c(1, 2, c(3,4)) \n x <- c(y, 5, c(6,7))', vector([1, 2, 3, 4, 5, 6, 7])); + testResolve('Simple Vector (int)', '2@x', 'x <- c(1, 2, 3, 4) \n x', vector([1, 2, 3, 4])); + testResolve('Simple Vector (string)', '2@x', 'x <- c("a", "b", "c", "d") \n x', vector(['a', 'b', 'c', 'd'])); + testResolve('Vector with alias', '2@x', 'y <- 1 \n x <- c(y,2)', vector([1, 2])); + testResolve('Vector in vector', '1@x', 'x <- c(1, 2, c(3, 4, 5))', vector([1, 2, 3, 4, 5])); + testResolve('Vector in vector alias', '2@x', 'y <- c(1, 2, c(3,4)) \n x <- c(y, 5, c(6,7))', vector([1, 2, 3, 4, 5, 6, 7])); - testResolve('c aliased', '2@x', 'f <- c \n x <- f(1,2,3)', vector([1,2,3])); - testResolve('c aliased deeply', '3@x', 'f <- c \n g <- f \n x <- g(1,2,3)', vector([1,2,3])); - }); - - describe('Resolve (vectors replacement operators)', () => { - testResolve('simple', '2@x', 'x <- c(1,2,3) \n x$b <- 1', Top); - }); + testResolve('c aliased', '2@x', 'f <- c \n x <- f(1,2,3)', vector([1,2,3])); + testResolve('c aliased deeply', '3@x', 'f <- c \n g <- f \n x <- g(1,2,3)', vector([1,2,3])); - describe('ByName', () => { - test(label('Locally without distracting elements', ['global-scope', 'lexicographic-scope'], ['other']), () => { - const xVar = variable('x', '_1'); - const env = defaultEnv().defineInEnv(xVar); - const result = resolveByName('x', env, ReferenceType.Unknown); - guard(result !== undefined, 'there should be a result'); - expect(result, 'there should be exactly one definition for x').to.have.length(1); - expect(result[0], 'it should be x').to.deep.equal(xVar); + // Empty Vectors + testResolve('Empty Vector', '1@x', 'x <- c()', vector([])); + testResolve('Empty Vector direct', '1@c', 'c()', vector([])); }); - test(label('Locally with global distract', ['global-scope', 'lexicographic-scope'], ['other']), () => { - let env = defaultEnv() - .defineVariable('x', '_2', '_1'); - const xVar = variable('x', '_1'); - env = env.defineInEnv(xVar); - const result = resolveByName('x', env, ReferenceType.Unknown); - guard(result !== undefined, 'there should be a result'); - expect(result, 'there should be exactly one definition for x').to.have.length(1); - expect(result[0], 'it should be x').to.be.deep.equal(xVar); + + describe('Resolve (vectors replacement operators)', () => { + testResolve('simple', '2@x', 'x <- c(1,2,3) \n x$b <- 1', Top); }); - describe('Resolve Function', () => { - test(label('Locally without distracting elements', ['global-scope', 'lexicographic-scope', 'search-type'], ['other']), () => { - const xVar = variable('foo', '_1'); + + describe('ByName', () => { + test(label('Locally without distracting elements', ['global-scope', 'lexicographic-scope'], ['other']), () => { + const xVar = variable('x', '_1'); const env = defaultEnv().defineInEnv(xVar); - const result = resolveByName('foo', env, ReferenceType.Function); - assert.isUndefined(result, 'there should be no result'); + const result = resolveByName('x', env, ReferenceType.Unknown); + guard(result !== undefined, 'there should be a result'); + expect(result, 'there should be exactly one definition for x').to.have.length(1); + expect(result[0], 'it should be x').to.deep.equal(xVar); }); - }); - describe('Resolve Variable', () => { - test(label('Locally without distracting elements', ['global-scope', 'lexicographic-scope', 'search-type'], ['other']), () => { - const xVar = asFunction('foo', '_1'); - const env = defaultEnv().defineInEnv(xVar); - const result = resolveByName('foo', env, ReferenceType.Variable); - assert.isUndefined(result, 'there should be no result'); + test(label('Locally with global distract', ['global-scope', 'lexicographic-scope'], ['other']), () => { + let env = defaultEnv() + .defineVariable('x', '_2', '_1'); + const xVar = variable('x', '_1'); + env = env.defineInEnv(xVar); + const result = resolveByName('x', env, ReferenceType.Unknown); + guard(result !== undefined, 'there should be a result'); + expect(result, 'there should be exactly one definition for x').to.have.length(1); + expect(result[0], 'it should be x').to.be.deep.equal(xVar); + }); + describe('Resolve Function', () => { + test(label('Locally without distracting elements', ['global-scope', 'lexicographic-scope', 'search-type'], ['other']), () => { + const xVar = variable('foo', '_1'); + const env = defaultEnv().defineInEnv(xVar); + const result = resolveByName('foo', env, ReferenceType.Function); + assert.isUndefined(result, 'there should be no result'); + }); }); + describe('Resolve Variable', () => { + test(label('Locally without distracting elements', ['global-scope', 'lexicographic-scope', 'search-type'], ['other']), () => { + const xVar = asFunction('foo', '_1'); + const env = defaultEnv().defineInEnv(xVar); + const result = resolveByName('foo', env, ReferenceType.Variable); + assert.isUndefined(result, 'there should be no result'); + }); + }); + }); }); + describe('Builtin Constants', () => { // Always Resolve test.each([ diff --git a/test/functionality/dataflow/query/resolve-value-query.test.ts b/test/functionality/dataflow/query/resolve-value-query.test.ts index 53d1b2be7ba..9438e768597 100644 --- a/test/functionality/dataflow/query/resolve-value-query.test.ts +++ b/test/functionality/dataflow/query/resolve-value-query.test.ts @@ -13,6 +13,7 @@ import { Top } from '../../../../src/dataflow/eval/values/r-value'; import type { ResolveResult } from '../../../../src/dataflow/eval/resolve/alias-tracking'; import { withTreeSitter } from '../../_helper/shell'; +import { vectorFrom } from '../../../../src/dataflow/eval/values/vectors/vector-constants'; describe('Resolve Value Query', withTreeSitter( parser => { function testQuery(name: string, code: string, criteria: SlicingCriteria, expected: ResolveResult[][]) { @@ -41,6 +42,7 @@ describe('Resolve Value Query', withTreeSitter( parser => { testQuery('Intermediary', 'x <- 1\ny <- x\nprint(y)', ['3@y'], [[setFrom(intervalFrom(1,1))]]); testQuery('Mystic Intermediary', 'x <- 1\ny <- f(x)\nprint(y)', ['3@y'], [[Top]]); testQuery('Either or', 'if(u) { x <- 1 } else { x <- 2 }\nprint(x)', ['2@x'], [[setFrom(intervalFrom(2,2), intervalFrom(1,1))]]); + testQuery('Vector', 'x <- c(1,2,3)', ['1@x'], [[setFrom(vectorFrom([intervalFrom(1,1), intervalFrom(2,2),intervalFrom(3,3)]))]]); testQuery('Big vector', `results <- c("A", "B", "C", "D", "E") col <- vector() From 78f7998b0b07f34218f66ac88f7b6261dc94b36b Mon Sep 17 00:00:00 2001 From: snh21 Date: Wed, 6 Aug 2025 17:24:07 +0200 Subject: [PATCH 4/4] refactor(alias-tracking): remove full - #1748 --- src/control-flow/cfg-dead-code.ts | 9 ++- src/dataflow/eval/resolve/alias-tracking.ts | 58 ++++++++++++------- src/dataflow/eval/resolve/resolve-argument.ts | 11 ++-- src/dataflow/eval/resolve/resolve.ts | 2 +- .../resolve-value-query-executor.ts | 2 +- .../dataflow/environments/resolve.test.ts | 7 +-- 6 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/control-flow/cfg-dead-code.ts b/src/control-flow/cfg-dead-code.ts index 49d754dbae6..9978c8b671b 100644 --- a/src/control-flow/cfg-dead-code.ts +++ b/src/control-flow/cfg-dead-code.ts @@ -1,6 +1,5 @@ /* currently this does not do work on function definitions */ -import type { CfgMidMarkerVertex, ControlFlowInformation } from './control-flow-graph'; -import { CfgEdgeType } from './control-flow-graph'; +import { CfgEdgeType, type ControlFlowInformation } from './control-flow-graph'; import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id'; import { Ternary } from '../util/logic'; import type { CfgPassInfo } from './cfg-simplification'; @@ -70,7 +69,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { } private handleValuesFor(id: NodeId, valueId: NodeId): void { - const values = valueSetGuard(resolveIdToValue(valueId, { graph: this.config.dfg, full: true, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables })); + const values = valueSetGuard(resolveIdToValue(valueId, { graph: this.config.dfg, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables })); if(values === undefined || values.elements.length !== 1 || values.elements[0].type != 'logical' || !isValue(values.elements[0].value)) { this.unableToCalculateValue(id); return; @@ -93,7 +92,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { return undefined; } - const values = valueSetGuard(resolveIdToValue(data.call.args[0].nodeId, { graph: this.config.dfg, full: true, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables })); + const values = valueSetGuard(resolveIdToValue(data.call.args[0].nodeId, { graph: this.config.dfg, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables })); if(values === undefined || values.elements.length !== 1 || values.elements[0].type != 'logical' || !isValue(values.elements[0].value)) { return undefined; } @@ -130,7 +129,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { return; } - const values = valueSetGuard(resolveIdToValue(data.vector.nodeId, { graph: this.config.dfg, environment: data.call.environment, full: true, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables })); + const values = valueSetGuard(resolveIdToValue(data.vector.nodeId, { graph: this.config.dfg, environment: data.call.environment, idMap: this.config.normalizedAst.idMap, resolve: this.config.flowrConfig.solver.variables })); if(values === undefined) { return; } diff --git a/src/dataflow/eval/resolve/alias-tracking.ts b/src/dataflow/eval/resolve/alias-tracking.ts index 582bc26122f..ac814bca69d 100644 --- a/src/dataflow/eval/resolve/alias-tracking.ts +++ b/src/dataflow/eval/resolve/alias-tracking.ts @@ -7,7 +7,8 @@ import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; import { envFingerprint } from '../../../slicing/static/fingerprint'; import { VisitingQueue } from '../../../slicing/static/visiting-queue'; import { guard } from '../../../util/assert'; -import { BuiltInEvalHandlerMapper, BuiltInMappingName, BuiltInProcessorMapper, type BuiltInIdentifierConstant } from '../../environments/built-in'; +import type { BuiltInMappingName , BuiltInIdentifierConstant } from '../../environments/built-in'; +import { BuiltInEvalHandlerMapper } from '../../environments/built-in'; import type { REnvironmentInformation } from '../../environments/environment'; import { initializeCleanEnvironments } from '../../environments/environment'; import type { Identifier } from '../../environments/identifier'; @@ -44,8 +45,6 @@ export interface ResolveInfo { idMap?: AstIdMap; /** The graph to resolve in */ graph?: DataflowGraph; - /** Whether to track variables */ - full?: boolean; /** Variable resolve mode */ resolve: VariableResolve; } @@ -148,7 +147,11 @@ export function getAliases(sourceIds: readonly NodeId[], dataflow: DataflowGraph * @param full - Whether to track aliases on resolve * @param resolve - Variable resolve mode */ -export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { environment, graph, idMap, full = true, resolve } : ResolveInfo): ResolveResult { +export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { environment, graph, idMap, resolve } : ResolveInfo): ResolveResult { + if(resolve === VariableResolve.Disabled) { + return Top; + } + if(id === undefined) { return Top; } @@ -159,25 +162,36 @@ export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { env return Top; } - switch(node.type) { - case RType.Argument: - return resolveIdToValue(node.value, { environment, graph, idMap, full, resolve }); - case RType.Symbol: - if(environment) { - return full ? trackAliasInEnvironments(resolve, node.lexeme, environment, graph, idMap) : Top; - } else if(graph && resolve === VariableResolve.Alias) { - return full ? trackAliasesInGraph(resolve, node.info.id, graph, idMap) : Top; - } else { + if(resolve === VariableResolve.Alias) { + switch(node.type) { + case RType.Argument: + return resolveIdToValue(node.value, { environment, graph, idMap, resolve }); + case RType.Symbol: + if(environment) { + return trackAliasInEnvironments(resolve, node.lexeme, environment, graph, idMap); + } else if(graph) { + return trackAliasesInGraph(resolve, node.info.id, graph, idMap) ; + } else { + return Top; + } + case RType.FunctionCall: + case RType.String: + case RType.Number: + case RType.Logical: + return setFrom(resolveNode(resolve, node, environment, graph, idMap)); + default: return Top; - } - case RType.FunctionCall: - case RType.String: - case RType.Number: - case RType.Logical: - return setFrom(resolveNode(resolve, node, environment, graph, idMap)); - default: - return Top; - } + } + } else if(resolve === VariableResolve.Builtin) { + switch(node.type) { + case RType.String: + case RType.Number: + case RType.Logical: + return setFrom(resolveNode(resolve, node, environment, graph, idMap)); + } + } + + return Top; } /** diff --git a/src/dataflow/eval/resolve/resolve-argument.ts b/src/dataflow/eval/resolve/resolve-argument.ts index 02c62b32fd7..e137507714d 100644 --- a/src/dataflow/eval/resolve/resolve-argument.ts +++ b/src/dataflow/eval/resolve/resolve-argument.ts @@ -14,7 +14,7 @@ import { resolveIdToValue } from './alias-tracking'; import { isValue } from '../values/r-value'; import { RFalse, RTrue } from '../../../r-bridge/lang-4.x/convert-values'; import { collectStrings } from '../values/string/string-constants'; -import type { VariableResolve } from '../../../config'; +import { VariableResolve } from '../../../config'; /** * Get the values of all arguments matching the criteria. @@ -92,9 +92,8 @@ function hasCharacterOnly(variableResolve: VariableResolve, graph: DataflowGraph } function resolveBasedOnConfig(variableResolve: VariableResolve, graph: DataflowGraph, vertex: DataflowGraphVertexFunctionCall, argument: RNodeWithParent, environment: REnvironmentInformation | undefined, idMap: Map | undefined, resolveValue : boolean | 'library' | undefined): string[] | undefined { - let full = true; if(!resolveValue) { - full = false; + variableResolve = VariableResolve.Builtin; } if(resolveValue === 'library') { @@ -103,11 +102,11 @@ function resolveBasedOnConfig(variableResolve: VariableResolve, graph: DataflowG if(argument.type === RType.Symbol) { return [argument.lexeme]; } - full = false; + variableResolve = VariableResolve.Builtin; } } - const resolved = valueSetGuard(resolveIdToValue(argument, { environment, graph, full, resolve: variableResolve })); + const resolved = valueSetGuard(resolveIdToValue(argument, { environment, graph, resolve: variableResolve })); if(resolved) { const values: string[] = []; for(const value of resolved.elements) { @@ -118,7 +117,7 @@ function resolveBasedOnConfig(variableResolve: VariableResolve, graph: DataflowG } else if(value.type === 'logical' && isValue(value.value)) { values.push(value.value.valueOf() ? RTrue : RFalse); } else if(value.type === 'vector' && isValue(value.elements)) { - const elements = collectStrings(value.elements, !full); + const elements = collectStrings(value.elements, variableResolve === VariableResolve.Builtin); if(elements === undefined) { return undefined; } diff --git a/src/dataflow/eval/resolve/resolve.ts b/src/dataflow/eval/resolve/resolve.ts index 8e0c39ed080..7b9dc4fd895 100644 --- a/src/dataflow/eval/resolve/resolve.ts +++ b/src/dataflow/eval/resolve/resolve.ts @@ -81,7 +81,7 @@ export function resolveAsVector(resolve: VariableResolve, a: RNodeWithParent, en if(arg.value.type === RType.Symbol) { - const value = resolveIdToValue(arg.info.id, { environment: env, idMap: map, graph: graph, full: true, resolve }); + const value = resolveIdToValue(arg.info.id, { environment: env, idMap: map, graph: graph, resolve }); if(isTop(value)) { return Top; } diff --git a/src/queries/catalog/resolve-value-query/resolve-value-query-executor.ts b/src/queries/catalog/resolve-value-query/resolve-value-query-executor.ts index 6eb75f2eb1d..e543007f110 100644 --- a/src/queries/catalog/resolve-value-query/resolve-value-query-executor.ts +++ b/src/queries/catalog/resolve-value-query/resolve-value-query-executor.ts @@ -20,7 +20,7 @@ export function executeResolveValueQuery({ dataflow: { graph }, ast, config }: B const values = query.criteria .map(criteria => slicingCriterionToId(criteria, ast.idMap)) - .flatMap(ident => resolveIdToValue(ident, { graph, full: true, idMap: ast.idMap, resolve: config.solver.variables })); + .flatMap(ident => resolveIdToValue(ident, { graph, idMap: ast.idMap, resolve: config.solver.variables })); results[key] = { values: values diff --git a/test/functionality/dataflow/environments/resolve.test.ts b/test/functionality/dataflow/environments/resolve.test.ts index 9187696ceb8..95a60f6a6f6 100644 --- a/test/functionality/dataflow/environments/resolve.test.ts +++ b/test/functionality/dataflow/environments/resolve.test.ts @@ -17,7 +17,7 @@ import { intervalFromValues } from '../../../../src/dataflow/eval/values/interva import { getScalarFromInteger } from '../../../../src/dataflow/eval/values/scalar/scalar-consatnts'; import { vectorFrom } from '../../../../src/dataflow/eval/values/vectors/vector-constants'; import { resolveIdToValue, resolveToConstants } from '../../../../src/dataflow/eval/resolve/alias-tracking'; -import { defaultConfigOptions } from '../../../../src/config'; +import { defaultConfigOptions, VariableResolve } from '../../../../src/config'; enum Allow { None = 0, @@ -61,8 +61,7 @@ describe('Resolve', withTreeSitter(shell => { environment: resolveWith === 'env' ? dataflow.environment : undefined, graph: dataflow.graph, idMap: normalize.idMap, - full: true, - resolve: defaultConfigOptions.solver.variables + resolve: VariableResolve.Alias }); if((allow & Allow.Top) == Allow.Top && isTop(resolved)) { @@ -84,7 +83,7 @@ describe('Resolve', withTreeSitter(shell => { 'u <- u + 1', `if(FALSE) { rm(${identifier})}` ]; - + describe(name, () => { for(const distractor of distractors) { const mutatedCode = code.split('\n').map(line => `${distractor}\n${line}`).join('\n');