Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/control-flow/cfg-simplification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export interface CfgPassInfo {
}
export type CfgSimplificationPass = (cfg: ControlFlowInformation, info: CfgPassInfo) => ControlFlowInformation;

/**
* All available control flow graph simplification passes.
*/
export const CfgSimplificationPasses = {
'unique-cf-sets': uniqueControlFlowSets,
'analyze-dead-code': cfgAnalyzeDeadCode,
Expand Down
21 changes: 13 additions & 8 deletions src/dataflow/graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import {
type DataflowGraphVertexFunctionCall,
type DataflowGraphVertexFunctionDefinition,
type DataflowGraphVertexInfo,
type DataflowGraphVertices
, VertexType } from './vertex';
type DataflowGraphVertices,
VertexType
} from './vertex';
import { uniqueArrayMerge } from '../../util/collections/arrays';
import { EmptyArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
import type { Identifier, IdentifierDefinition, IdentifierReference } from '../environments/identifier';
import { type NodeId , normalizeIdToNumberIfPossible } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { type IEnvironment, type REnvironmentInformation , initializeCleanEnvironments } from '../environments/environment';
import { type NodeId, normalizeIdToNumberIfPossible } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import {
type IEnvironment,
initializeCleanEnvironments,
type REnvironmentInformation
} from '../environments/environment';
import type { AstIdMap } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import { cloneEnvironmentInformation } from '../environments/clone';
import { jsonReplacer } from '../../util/json';
Expand Down Expand Up @@ -108,11 +113,11 @@ export interface DataflowGraphJson {

/**
* An unknown side effect describes something that we cannot handle correctly (in all cases).
* For example, `eval` will be marked as an unknown side effect as we have no idea of how it will affect the program.
* For example, `load` will be marked as an unknown side effect as we have no idea of how it will affect the program.
* Linked side effects are used whenever we know that a call may be affected by another one in a way that we cannot
* grasp from the dataflow perspective (e.g., an indirect dependency based on the currently active graphic device).
*/
export type UnknownSidEffect = NodeId | { id: NodeId, linkTo: LinkTo<RegExp> }
export type UnknownSideEffect = NodeId | { id: NodeId, linkTo: LinkTo<RegExp> }

/**
* The dataflow graph holds the dataflow information found within the given AST.
Expand Down Expand Up @@ -141,7 +146,7 @@ export class DataflowGraph<
* As a (temporary) solution until we have FD edges, a side effect may also store known target links
* that have to be/should be resolved (as globals) as a separate pass before the df analysis ends.
*/
private readonly _unknownSideEffects = new Set<UnknownSidEffect>();
private readonly _unknownSideEffects = new Set<UnknownSideEffect>();

constructor(idMap: AstIdMap | undefined) {
DataflowGraph.DEFAULT_ENVIRONMENT ??= initializeCleanEnvironments();
Expand Down Expand Up @@ -222,7 +227,7 @@ export class DataflowGraph<
/**
* Retrieves the set of vertices which have side effects that we do not know anything about.
*/
public get unknownSideEffects(): Set<UnknownSidEffect> {
public get unknownSideEffects(): Set<UnknownSideEffect> {
return this._unknownSideEffects;
}

Expand Down
6 changes: 3 additions & 3 deletions src/documentation/doc-util/doc-dfg.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DataflowGraph, UnknownSidEffect } from '../../dataflow/graph/graph';
import type { DataflowGraph, UnknownSideEffect } from '../../dataflow/graph/graph';
import type { RShell } from '../../r-bridge/shell';
import { type MermaidMarkdownMark , graphToMermaid } from '../../util/mermaid/dfg';
import { graphToMermaid, type MermaidMarkdownMark } from '../../util/mermaid/dfg';
import { PipelineExecutor } from '../../core/pipeline-executor';
import { createDataflowPipeline, DEFAULT_DATAFLOW_PIPELINE } from '../../core/steps/pipeline/default-pipelines';
import { deterministicCountingIdGenerator } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
Expand Down Expand Up @@ -44,7 +44,7 @@ export interface PrintDataflowGraphOptions {
/**
* Visualizes a side effect for documentation purposes.
*/
export function formatSideEffect(ef: UnknownSidEffect): string {
export function formatSideEffect(ef: UnknownSideEffect): string {
if(typeof ef === 'object') {
return `${ef.id} (linked)`;
} else {
Expand Down
58 changes: 16 additions & 42 deletions src/project/cache/flowr-analyzer-cache.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import type { KnownParser } from '../../r-bridge/parser';
import { type CacheInvalidationEvent , CacheInvalidationEventType, FlowrCache } from './flowr-cache';
import { type CacheInvalidationEvent, CacheInvalidationEventType, FlowrCache } from './flowr-cache';
import {
createDataflowPipeline,
type DEFAULT_DATAFLOW_PIPELINE,
type TREE_SITTER_DATAFLOW_PIPELINE
, createDataflowPipeline } from '../../core/steps/pipeline/default-pipelines';
} from '../../core/steps/pipeline/default-pipelines';
import type { PipelineExecutor } from '../../core/pipeline-executor';
import type { IdGenerator } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import type { NoInfo } from '../../r-bridge/lang-4.x/ast/model/model';
import type { TreeSitterExecutor } from '../../r-bridge/lang-4.x/tree-sitter/tree-sitter-executor';
import type { PipelineOutput } from '../../core/steps/pipeline/pipeline';
import { assertUnreachable, guard } from '../../util/assert';
import { ObjectMap } from '../../util/collections/objectmap';
import type { CfgSimplificationPassName } from '../../control-flow/cfg-simplification';
import type { ControlFlowInformation } from '../../control-flow/control-flow-graph';
import { extractCfg, extractCfgQuick } from '../../control-flow/extract-cfg';
import { CfgKind } from '../cfg-kind';
import type { CfgKind } from '../cfg-kind';
import type { FlowrAnalyzerContext } from '../context/flowr-analyzer-context';
import { FlowrAnalyzerControlFlowCache } from './flowr-analyzer-controlflow-cache';

interface FlowrAnalyzerCacheOptions<Parser extends KnownParser> {
parser: Parser;
Expand All @@ -31,18 +31,14 @@ type AnalyzerPipelineExecutor<Parser extends KnownParser> = PipelineExecutor<Ana
export type AnalyzerCacheType<Parser extends KnownParser> = Parser extends TreeSitterExecutor ? Partial<PipelineOutput<typeof TREE_SITTER_DATAFLOW_PIPELINE>>
: Partial<PipelineOutput<typeof DEFAULT_DATAFLOW_PIPELINE>>;

interface ControlFlowCache {
simplified: ObjectMap<[passes: readonly CfgSimplificationPassName[], kind: CfgKind], ControlFlowInformation>,
}

/**
* This provides the full analyzer caching layer, please avoid using this directly
* and prefer the {@link FlowrAnalyzer}.
*/
export class FlowrAnalyzerCache<Parser extends KnownParser> extends FlowrCache<AnalyzerCacheType<Parser>> {
private args: FlowrAnalyzerCacheOptions<Parser>;
private pipeline: AnalyzerPipelineExecutor<Parser> = undefined as unknown as AnalyzerPipelineExecutor<Parser>;
private controlFlowCache: ControlFlowCache = undefined as unknown as ControlFlowCache;
private controlFlowCache: FlowrAnalyzerControlFlowCache = undefined as unknown as FlowrAnalyzerControlFlowCache;

protected constructor(args: FlowrAnalyzerCacheOptions<Parser>) {
super();
Expand All @@ -55,9 +51,7 @@ export class FlowrAnalyzerCache<Parser extends KnownParser> extends FlowrCache<A
context: this.args.context,
getId: this.args.getId
}) as AnalyzerPipelineExecutor<Parser>;
this.controlFlowCache = {
simplified: new ObjectMap<[readonly CfgSimplificationPassName[], CfgKind], ControlFlowInformation>(),
};
this.controlFlowCache = new FlowrAnalyzerControlFlowCache();
}

public static create<Parser extends KnownParser>(data: FlowrAnalyzerCacheOptions<Parser>): FlowrAnalyzerCache<Parser> {
Expand Down Expand Up @@ -161,34 +155,14 @@ export class FlowrAnalyzerCache<Parser extends KnownParser> extends FlowrCache<A
* @param kind - The kind of CFG that is requested.
* @param simplifications - Simplification passes to be applied to the CFG.
*/
public async controlflow(force: boolean | undefined, kind: CfgKind, simplifications: readonly CfgSimplificationPassName[] | undefined): Promise<ControlFlowInformation> {
guard(kind === CfgKind.Quick ? simplifications === undefined : true, 'Cannot apply simplifications to quick CFG');
simplifications ??= [];
if(!force) {
const value = this.controlFlowCache.simplified.get([simplifications, kind]);
if(value !== undefined) {
return value;
}
}

const normalized = await this.normalize(force);

let result: ControlFlowInformation;
switch(kind) {
case CfgKind.WithDataflow:
result = extractCfg(normalized, this.args.context, (await this.dataflow()).graph, simplifications);
break;
case CfgKind.NoDataflow:
result = extractCfg(normalized, this.args.context, undefined, simplifications);
break;
case CfgKind.Quick:
result = this.peekDataflow()?.cfgQuick ?? extractCfgQuick(normalized);
break;

}

this.controlFlowCache.simplified.set([simplifications, kind], result);
return result;
public async controlflow(force: boolean | undefined, kind: CfgKind, simplifications?: readonly CfgSimplificationPassName[]): Promise<ControlFlowInformation> {
const cfgInfo = {
ctx: this.args.context,
cfgQuick: this.peekDataflow()?.cfgQuick,
ast: async() => await this.normalize(),
dfg: async() => await this.dataflow()
};
return this.controlFlowCache.get(force, kind, cfgInfo, simplifications);
}

/**
Expand All @@ -198,6 +172,6 @@ export class FlowrAnalyzerCache<Parser extends KnownParser> extends FlowrCache<A
* @see {@link FlowrAnalyzerCache#controlflow} - to get the control flow graph, computing if necessary.
*/
public peekControlflow(kind: CfgKind, simplifications: readonly CfgSimplificationPassName[] | undefined): ControlFlowInformation | undefined {
return this.controlFlowCache.simplified.get([simplifications ?? [], kind]);
return this.controlFlowCache.peek(kind, simplifications);
}
}
103 changes: 103 additions & 0 deletions src/project/cache/flowr-analyzer-controlflow-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { ObjectMap } from '../../util/collections/objectmap';
import type { CfgSimplificationPassName } from '../../control-flow/cfg-simplification';
import { simplifyControlFlowInformation } from '../../control-flow/cfg-simplification';
import { CfgKind } from '../cfg-kind';
import type { ControlFlowInformation } from '../../control-flow/control-flow-graph';
import { guard } from '../../util/assert';
import { extractCfg, extractCfgQuick } from '../../control-flow/extract-cfg';
import type { NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import type { DataflowInformation } from '../../dataflow/info';
import type { FlowrAnalyzerContext } from '../context/flowr-analyzer-context';

type ControlFlowCache = ObjectMap<[passes: readonly CfgSimplificationPassName[], kind: CfgKind], ControlFlowInformation>;

interface CfgInfo {
ctx: FlowrAnalyzerContext,
cfgQuick: ControlFlowInformation | undefined
dfg: () => Promise<DataflowInformation>,
ast: () => Promise<NormalizedAst>,
}

export class FlowrAnalyzerControlFlowCache {
private readonly cache: ControlFlowCache = new ObjectMap<[readonly CfgSimplificationPassName[], CfgKind], ControlFlowInformation>();

public peek(kind: CfgKind, simplifications: readonly CfgSimplificationPassName[] | undefined): ControlFlowInformation | undefined {
return this.cache.get([simplifications ?? [], kind]);
}

public async get(
force: boolean | undefined,
kind: CfgKind,
cfgCacheInfo: CfgInfo,
simplifications?: readonly CfgSimplificationPassName[]
): Promise<ControlFlowInformation> {
guard(kind === CfgKind.Quick ? simplifications === undefined : true, 'Cannot apply simplifications to quick CFG');
simplifications ??= [];
const orderedSimplifications = this.normalizeSimplificationOrder(simplifications);

const cached = force ?
{ cfg: undefined, missingSimplifications: orderedSimplifications }
: this.tryGetCachedCfg(orderedSimplifications, kind);
let cfg = cached.cfg;

if(!cfg) {
cfg = await this.createAndCacheBaseCfg(kind, cfgCacheInfo);
}

if(cached.missingSimplifications.length > 0) {
const cfgPassInfo = { dfg: (await cfgCacheInfo.dfg()).graph, ctx: cfgCacheInfo.ctx, ast: await cfgCacheInfo.ast() };
cfg = simplifyControlFlowInformation(cfg, cfgPassInfo, cached.missingSimplifications);
}

this.cache.set([orderedSimplifications, kind], cfg);
return cfg;
}

/**
* Create and cache the base CFG without simplifications.
*/
private async createAndCacheBaseCfg(kind: CfgKind, { cfgQuick, dfg, ctx, ast }: CfgInfo): Promise<ControlFlowInformation> {
let result: ControlFlowInformation;
switch(kind) {
case CfgKind.WithDataflow:
result = extractCfg(await ast(), ctx, (await dfg()).graph);
break;
case CfgKind.NoDataflow:
result = extractCfg(await ast(), ctx);
break;
case CfgKind.Quick:
result = cfgQuick ?? extractCfgQuick(await ast());
break;
}
this.cache.set([[], kind], result);
return result;
}

/**
* Try to get a cached CFG with some of the requested simplifications already applied.
* Matches the longest prefix of simplifications available.
* @returns The cached CFG and the missing simplifications to be applied, or `undefined` if no cached CFG is available.
*/
private tryGetCachedCfg(simplifications: readonly CfgSimplificationPassName[], kind: CfgKind): { cfg: ControlFlowInformation | undefined, missingSimplifications: readonly CfgSimplificationPassName[] } {
for(let prefixLen = simplifications.length; prefixLen >= 0; prefixLen--) {
const prefix = simplifications.slice(0, prefixLen);
const cached = this.cache.get([prefix, kind]);
if(cached !== undefined) {
return {
cfg: cached,
missingSimplifications: simplifications.slice(prefixLen)
};
}
}
return { cfg: undefined, missingSimplifications: simplifications };
}

/**
* Normalize the order of simplification passes.
* Is currently an identity function, but may be extended in the future to enforce a specific order using heuristics.
* @param simplifications - the requested simplification passes.
*/
private normalizeSimplificationOrder(simplifications: readonly CfgSimplificationPassName[]): readonly CfgSimplificationPassName[] {
return simplifications;
}
}
11 changes: 11 additions & 0 deletions test/functionality/documentation/doc-escape.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { assert, describe, test } from 'vitest';
import { escapeNewline } from '../../../src/documentation/doc-util/doc-escape';

describe('Document Escape Tests', () => {
test(() => {
const input = 'Line 1\nLine 2\rLine 3\r\nLine 4';
const expectedOutput = 'Line 1\\nLine 2\\rLine 3\\r\\nLine 4';
const result = escapeNewline(input);
assert.strictEqual(result, expectedOutput);
});
});
Loading
Loading