Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/control-flow/cfg-dead-code.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* currently this does not do work on function definitions */
import type { 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';
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -125,6 +124,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, 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.cachedConditions.set(data.call.id, isEmptyVector ? Ternary.Never : Ternary.Always);
}

protected onDefaultFunctionCall(data: { call: DataflowGraphVertexFunctionCall; }): void {
this.handleFunctionCall(data);
}
Expand Down
2 changes: 1 addition & 1 deletion src/dataflow/environments/built-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OtherInfo>(
name: RSymbol<OtherInfo & ParentInformation>,
Expand Down
119 changes: 89 additions & 30 deletions src/dataflow/eval/resolve/alias-tracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 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';
Expand All @@ -19,7 +20,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';
Expand All @@ -43,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;
}
Expand Down Expand Up @@ -147,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;
}
Expand All @@ -158,25 +162,36 @@ export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { env
return Top;
}

switch(node.type) {
case RType.Argument:
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;
} 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:
return setFrom(resolveNode(resolve, node, environment, graph, idMap));
case RType.String:
case RType.Number:
case RType.Logical:
return setFrom(valueFromRNodeConstant(node));
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;
}

/**
Expand Down Expand Up @@ -295,6 +310,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<BuiltInMappingName>(['builtin:replacement']);

/**
* Please use {@link resolveIdToValue}
*
Expand All @@ -305,11 +325,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();
Expand Down Expand Up @@ -352,30 +381,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<Value> = new Set<Value>();
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);
Expand Down
11 changes: 5 additions & 6 deletions src/dataflow/eval/resolve/resolve-argument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<NodeId, RNode> | undefined, resolveValue : boolean | 'library' | undefined): string[] | undefined {
let full = true;
if(!resolveValue) {
full = false;
variableResolve = VariableResolve.Builtin;
}

if(resolveValue === 'library') {
Expand All @@ -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) {
Expand All @@ -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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/dataflow/eval/resolve/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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[] = [];
Expand All @@ -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;
}
Expand Down
20 changes: 0 additions & 20 deletions src/dataflow/eval/values/general.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions test/functionality/control-flow/dead-code/cfg-dead-code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,25 @@
for(const [n, i] of [...reachableFromStart.map(n => [n, false] as const), ...unreachableFromStart.map(n => [n, true] as const)]) {
const resolved = tryResolveSliceCriterionToId(n, ast.idMap) ?? n;
if(i === canReach(cfg.graph, cfg.entryPoints, resolved)) {
throw new Error(`Expected node ${n} (${resolved}) to be ${i ? 'unreachable' : 'reachable'} from the start (${JSON.stringify(cfg.entryPoints)}), but it is not.`);

Check failure on line 27 in test/functionality/control-flow/dead-code/cfg-dead-code.test.ts

View workflow job for this annotation

GitHub Actions / ⚗️ Test Suite (coverage)

test/functionality/control-flow/dead-code/cfg-dead-code.test.ts > Control Flow Graph > Dead Code Removal > Empty Vector > f <- function(p = c()) { for(i in p) { x <- 2} }

Error: Expected node 1@x (11) to be reachable from the start ([19]), but it is not. ❯ Object.additionalAsserts test/functionality/control-flow/dead-code/cfg-dead-code.test.ts:27:13 ❯ test/functionality/_helper/controlflow/assert-control-flow-graph.ts:68:13
}
}
}
});
}


describe('Dead Code Removal', () => {
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'] });

// 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([
{ prefix: 'if(TRUE)', swap: false },
{ prefix: 'if(FALSE)', swap: true },
Expand Down
Loading
Loading