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: 23 additions & 3 deletions src/dataflow/graph/edge.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';

/**
* An edge consist of only of the type (source and target are encoded with the Dataflow Graph).
* Multiple edges are encoded by joining the respective type bits.
Expand All @@ -8,6 +10,16 @@ export interface DfEdge {
types: EdgeTypeBits
}

/**
* Control Flow edges additionally encode which node encodes the condition {@link DFControlFlowEdge.condition}, and
* when the condition {@link DFControlFlowEdge.condition} is met.
*/
export interface DFControlFlowEdge extends DfEdge {
types: EdgeType.ControlDependency,
when?: boolean,
condition: NodeId
}

/**
* Represents the relationship between the source and the target vertex in the dataflow graph.
* The actual value is represented as a bitmask, so please refer to {@link DfEdge} for helpful functions.
Expand Down Expand Up @@ -36,7 +48,11 @@ export enum EdgeType {
/** The edge determines that the source is a side effect that happens when the target is called */
SideEffectOnCall = 128,
/** The Edge determines that the reference is affected by a non-standard evaluation (e.g., a for-loop body or a quotation) */
NonStandardEvaluation = 256
NonStandardEvaluation = 256,
/** This edge determines that the source is evaluated before the target */
FlowDependency = 4096,
/** This edge determines that the target is evaluated based on a condition (e.g. branches of an if-else-statement) */
ControlDependency = 8192,
}

/**
Expand All @@ -51,7 +67,9 @@ export const enum EdgeTypeName {
DefinedByOnCall = 'defined-by-on-call',
Argument = 'argument',
SideEffectOnCall = 'side-effect-on-call',
NonStandardEvaluation = 'non-standard-evaluation'
NonStandardEvaluation = 'non-standard-evaluation',
FlowDependency = 'flow-dependency',
ControlDependency = 'control-dependency'
}

export type EdgeTypeBits = number;
Expand All @@ -65,7 +83,9 @@ const edgeTypeToHumanReadableName: ReadonlyMap<EdgeType, EdgeTypeName> = new Map
[EdgeType.DefinedByOnCall, EdgeTypeName.DefinedByOnCall ],
[EdgeType.Argument, EdgeTypeName.Argument ],
[EdgeType.SideEffectOnCall, EdgeTypeName.SideEffectOnCall ],
[EdgeType.NonStandardEvaluation, EdgeTypeName.NonStandardEvaluation]
[EdgeType.NonStandardEvaluation, EdgeTypeName.NonStandardEvaluation],
[EdgeType.FlowDependency, EdgeTypeName.FlowDependency],
[EdgeType.ControlDependency, EdgeTypeName.ControlDependency]
]);

type DfEdgeLike = { types: number };
Expand Down
10 changes: 7 additions & 3 deletions src/dataflow/graph/graph.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { guard } from '../../util/assert';
import type { DfEdge, EdgeType } from './edge';
import type { DFControlFlowEdge, DfEdge, EdgeType } from './edge';
import type { DataflowInformation } from '../info';
import {
type DataflowGraphVertexArgument,
Expand Down Expand Up @@ -432,7 +432,9 @@ export class DataflowGraph<
return this;
}

public addEdge(fromId: NodeId, toId: NodeId, type: EdgeType | number): this {
public addEdge(fromId: NodeId, toId: NodeId, type: EdgeType.ControlDependency, data: Omit<DFControlFlowEdge, 'types'>): this;
public addEdge(fromId: NodeId, toId: NodeId, type: Exclude<EdgeType, EdgeType.ControlDependency> | number): this;
public addEdge(fromId: NodeId, toId: NodeId, type: EdgeType | number, data?: Omit<DFControlFlowEdge, 'types'>): this {
if(fromId === toId) {
return this;
}
Expand All @@ -441,7 +443,7 @@ export class DataflowGraph<
const edgeInFrom = existingFrom?.get(toId);

if(edgeInFrom === undefined) {
const edge = { types: type } as unknown as Edge;
const edge = { types: type, ...data } as unknown as Edge;

if(existingFrom === undefined) {
this.edgeInformation.set(fromId, new Map([[toId, edge]]));
Expand All @@ -451,6 +453,7 @@ export class DataflowGraph<
} else {
// adding the type
edgeInFrom.types |= type;
Object.assign(edgeInFrom, data);
}
return this;
}
Expand Down Expand Up @@ -500,6 +503,7 @@ export class DataflowGraph<
if(existing === undefined) {
this.edgeInformation.set(id, new Map([[target, edge]]));
} else {
// TODO: Merge Attributes
const get = existing.get(target);
if(get === undefined) {
existing.set(target, edge);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,27 @@ export function processExpressionList<OtherInfo>(
}

const meId = withGroup ? rootId : (processedExpressions.find(isNotUndefined)?.entryPoint ?? rootId);

const firstArg = processedExpressions[0];
if(firstArg) {
nextGraph.addEdge(rootId, firstArg.entryPoint, EdgeType.FlowDependency);
}

for(let i = 0; i < processedExpressions.length - 1; i++) {
const current = processedExpressions[i];
const next = processedExpressions[i + 1];

if(current === undefined || next === undefined) {
continue;
}

console.log(`processing: ${current.entryPoint}`);
for(const exit of current.exitPoints) {
console.log(`EL Edge ${exit.nodeId} -> ${next.entryPoint}`);
nextGraph.addEdge(exit.nodeId, next.entryPoint, EdgeType.FlowDependency);
}
}

return {
/* no active nodes remain, they are consumed within the remaining read collection */
unknownReferences: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,21 @@ export function processIfThenElse<OtherInfo>(
origin: BuiltInProcName.IfThenElse
});

// as an if always evaluates its condition, we add a 'reads'-edge
nextGraph.addEdge(rootId, cond.entryPoint, EdgeType.Reads);
// as an if always evaluates its condition, we add a 'reads'-edge and 'flow-dependency'-edge
nextGraph.addEdge(rootId, cond.entryPoint, EdgeType.Reads | EdgeType.FlowDependency);

const exitPoints = (then?.exitPoints ?? []).map(e => ({ ...e, cds: makeThenMaybe ? [...data.cds ?? [], { id: rootId, when: true }] : e.cds }))
.concat((otherwise?.exitPoints ?? []).map(e => ({ ...e, cds: makeOtherwiseMaybe ? [...data.cds ?? [], { id: rootId, when: false }] : e.cds })));

if(then !== undefined) {
console.log(`then exits: ${JSON.stringify(then.exitPoints)}`);
nextGraph.addEdge(cond.entryPoint, then.entryPoint, EdgeType.ControlDependency, { condition: cond.entryPoint, when: true });
}

if(otherwise !== undefined) {
nextGraph.addEdge(cond.entryPoint, otherwise.entryPoint, EdgeType.ControlDependency, { condition: cond.entryPoint, when: false });
}

return {
unknownReferences: [],
in: [{ nodeId: rootId, name: name.content, cds: originalDependency, type: ReferenceType.Function }, ...ingoing],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,29 @@ export function processKnownFunctionCall<OtherInfo>(
}
}

// Connects Args in control flow
const firstArg = processedArguments[0];
if(firstArg) {
finalGraph.addEdge(rootId, firstArg.entryPoint, EdgeType.FlowDependency);
}

for(let i = 0; i < processedArguments.length - 1; i++) {
const current = processedArguments[i];
const next = processedArguments[i + 1];

if(current === undefined || next === undefined) {
continue;
}

for(const exit of current.exitPoints) {
console.log(`KC Edge ${exit.nodeId} -> ${next.entryPoint}`);
finalGraph.addEdge(exit.nodeId, next.entryPoint, EdgeType.FlowDependency);
}
}

console.log(`INF: ${rootId}, exit points: ${JSON.stringify(exitPoints)}`);
console.log(`INF EXT: ${rootId}, ${JSON.stringify(processedArguments[processArgs.length - 1]?.exitPoints)}`);

return {
information: {
unknownReferences: [],
Expand All @@ -147,7 +170,7 @@ export function processKnownFunctionCall<OtherInfo>(
graph: finalGraph,
environment: finalEnv,
entryPoint: rootId,
exitPoints: exitPoints ?? [{ nodeId: rootId, type: ExitPointType.Default, cds: data.cds }],
exitPoints: exitPoints ?? processedArguments[processArgs.length-1]?.exitPoints ?? [{ nodeId: rootId, type: ExitPointType.Default, cds: data.cds }],
hooks: functionName.hooks
},
callArgs,
Expand Down
1 change: 0 additions & 1 deletion src/slicing/criterion/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,3 @@ function conventionalCriteriaToId<OtherInfo>(line: number, name: string, dataflo
}
return candidate?.info.id;
}

14 changes: 12 additions & 2 deletions src/util/mermaid/dfg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
ReferenceTypeReverseMapping
} from '../../dataflow/environments/identifier';
import { EmptyArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
import { DfEdge, type EdgeType } from '../../dataflow/graph/edge';
import type { DFControlFlowEdge } from '../../dataflow/graph/edge';
import { DfEdge, EdgeType } from '../../dataflow/graph/edge';
import { type DataflowGraphVertexInfo, VertexType } from '../../dataflow/graph/vertex';
import type { IEnvironment } from '../../dataflow/environments/environment';
import { RType } from '../../r-bridge/lang-4.x/ast/model/type';
Expand Down Expand Up @@ -213,7 +214,7 @@ function vertexToMermaid(info: DataflowGraphVertexInfo, mermaid: MermaidGraph, i
if(!mermaid.presentEdges.has(edgeId)) {
mermaid.presentEdges.add(edgeId);
const style = NodeId.isBuiltIn(target) ? '-.->' : '-->';
mermaid.edgeLines.push(` ${idPrefix}${id} ${style}|"${[...edgeTypes].map(e => typeof e === 'number' ? DfEdge.typeToName(e) : e).join(', ')}${
mermaid.edgeLines.push(` ${idPrefix}${id} ${style}|"${[...edgeTypes].map(e => typeof e === 'number' ? DfEdge.typeToName(e) : e).join(', ')}${edgeTypes.has(EdgeType.ControlDependency) ? ((edge as DFControlFlowEdge).when ? ' (when: true) ' : ' (when: false) ') : ''}${
'file' in edge && edge.file ? `, from: ${edge.file}` : ''
}"| ${idPrefix}${target}`);
if(mermaid.mark?.has(id + '->' + target)) {
Expand All @@ -223,6 +224,15 @@ function vertexToMermaid(info: DataflowGraphVertexInfo, mermaid: MermaidGraph, i
if(edgeTypes.has('CD-True') || edgeTypes.has('CD-False')) {
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} stroke:gray,color:gray;`);
}
if(edgeTypes.has(EdgeType.FlowDependency)) {
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} stroke:red,color:red;`);
}
if(edgeTypes.has(EdgeType.ControlDependency) && (edge as DFControlFlowEdge).when) {
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} stroke:blue,color:blue;`);
}
if(edgeTypes.has(EdgeType.ControlDependency) && !(edge as DFControlFlowEdge).when) {
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} stroke:cyan,color:cyan;`);
}
if(NodeId.isBuiltIn(target)) {
mermaid.edgeLines.push(` linkStyle ${mermaid.presentEdges.size - 1} stroke:gray;`);
if(!mermaid.presentVertices.has(target)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ const EdgeTypeFnMap: Record<EdgeType, string | undefined> = {
[EdgeType.Argument]: 'argument',
[EdgeType.NonStandardEvaluation]: 'nse',
[EdgeType.SideEffectOnCall]: 'sideEffectOnCall',
[EdgeType.DefinedByOnCall]: 'definedByOnCall'
[EdgeType.DefinedByOnCall]: 'definedByOnCall',
[EdgeType.FlowDependency]: 'flowDependency',
[EdgeType.ControlDependency]: 'controlDependency'
};

class DataflowBuilderPrinter {
Expand Down
Loading