diff --git a/src/abstract-interpretation/data-frame/absint-visitor.ts b/src/abstract-interpretation/data-frame/absint-visitor.ts index 2cd7c416040..3463c43acd9 100644 --- a/src/abstract-interpretation/data-frame/absint-visitor.ts +++ b/src/abstract-interpretation/data-frame/absint-visitor.ts @@ -1,12 +1,28 @@ -import { type CfgBasicBlockVertex, type CfgSimpleVertex, type ControlFlowInformation, CfgVertexType, getVertexRootId, isMarkerVertex } from '../../control-flow/control-flow-graph'; -import { type SemanticCfgGuidedVisitorConfiguration, SemanticCfgGuidedVisitor } from '../../control-flow/semantic-cfg-guided-visitor'; +import { + type CfgBasicBlockVertex, + type CfgSimpleVertex, + CfgVertexType, + type ControlFlowInformation, + getVertexRootId, + isMarkerVertex +} from '../../control-flow/control-flow-graph'; +import { + SemanticCfgGuidedVisitor, + type SemanticCfgGuidedVisitorConfiguration +} from '../../control-flow/semantic-cfg-guided-visitor'; import type { DataflowGraph } from '../../dataflow/graph/graph'; import type { DataflowGraphVertexFunctionCall, DataflowGraphVertexVariableDefinition } from '../../dataflow/graph/vertex'; import type { NoInfo, RNode } from '../../r-bridge/lang-4.x/ast/model/model'; import type { NormalizedAst, ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { isNotUndefined } from '../../util/assert'; -import { type AbstractInterpretationInfo, DataFrameInfoMarker, hasDataFrameAssignmentInfo, hasDataFrameExpressionInfo, hasDataFrameInfoMarker } from './absint-info'; +import { + type AbstractInterpretationInfo, + DataFrameInfoMarker, + hasDataFrameAssignmentInfo, + hasDataFrameExpressionInfo, + hasDataFrameInfoMarker +} from './absint-info'; import { DataFrameDomain, DataFrameStateDomain } from './dataframe-domain'; import { mapDataFrameAccess } from './mappers/access-mapper'; import { isAssignmentTarget, mapDataFrameVariableAssignment } from './mappers/assignment-mapper'; @@ -100,7 +116,7 @@ export class DataFrameShapeInferenceVisitor< const sourceNode = this.getNormalizedAst(source); if(node !== undefined && isAssignmentTarget(targetNode) && sourceNode !== undefined) { - node.info.dataFrame = mapDataFrameVariableAssignment(targetNode, sourceNode, this.config.dfg); + node.info.dataFrame = mapDataFrameVariableAssignment(targetNode, sourceNode, this.config.dfg, this.config.ctx); this.applyDataFrameAssignment(node); this.clearUnassignedInfo(targetNode); } @@ -110,7 +126,7 @@ export class DataFrameShapeInferenceVisitor< const node = this.getNormalizedAst(call.id); if(node !== undefined) { - node.info.dataFrame = mapDataFrameAccess(node, this.config.dfg); + node.info.dataFrame = mapDataFrameAccess(node, this.config.dfg, this.config.ctx); this.applyDataFrameExpression(node); } } @@ -130,7 +146,7 @@ export class DataFrameShapeInferenceVisitor< const sourceNode = this.getNormalizedAst(source); if(node !== undefined && targetNode !== undefined && sourceNode !== undefined) { - node.info.dataFrame = mapDataFrameReplacementFunction(node, sourceNode, this.config.dfg); + node.info.dataFrame = mapDataFrameReplacementFunction(node, sourceNode, this.config.dfg, this.config.ctx); this.applyDataFrameExpression(node); this.clearUnassignedInfo(targetNode); } diff --git a/src/abstract-interpretation/data-frame/mappers/access-mapper.ts b/src/abstract-interpretation/data-frame/mappers/access-mapper.ts index 740ae9a32ef..9d2d83dca0e 100644 --- a/src/abstract-interpretation/data-frame/mappers/access-mapper.ts +++ b/src/abstract-interpretation/data-frame/mappers/access-mapper.ts @@ -3,12 +3,13 @@ import type { ResolveInfo } from '../../../dataflow/eval/resolve/alias-tracking' import type { DataflowGraph } from '../../../dataflow/graph/graph'; import type { RNode } from '../../../r-bridge/lang-4.x/ast/model/model'; import type { RAccess, RIndexAccess, RNamedAccess } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-access'; -import { type RFunctionArgument, EmptyArgument } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { EmptyArgument, type RFunctionArgument } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { ParentInformation } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate'; import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; import type { DataFrameExpressionInfo, DataFrameOperation } from '../absint-info'; import { resolveIdToArgValue, resolveIdToArgValueSymbolName, unquoteArgument } from '../resolve-args'; import { getArgumentValue, isDataFrameArgument } from './arguments'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; /** * Special named arguments of index-based access operators @@ -22,16 +23,18 @@ const SpecialAccessArgumentsMapper: Record = * Maps a concrete data frame access to abstract data frame operations. * @param node - The R node of the access * @param dfg - The data flow graph for resolving the arguments + * @param ctx - The read-only Flowr analyzer context * @returns Data frame expression info containing the mapped abstract data frame operations, or `undefined` if the node does not represent a data frame access */ export function mapDataFrameAccess( node: RNode, - dfg: DataflowGraph + dfg: DataflowGraph, + ctx: ReadOnlyFlowrAnalyzerContext ): DataFrameExpressionInfo | undefined { if(node.type !== RType.Access) { return; } - const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias }; + const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias, ctx }; let operations: DataFrameOperation[] | undefined; if(isStringBasedAccess(node)) { diff --git a/src/abstract-interpretation/data-frame/mappers/assignment-mapper.ts b/src/abstract-interpretation/data-frame/mappers/assignment-mapper.ts index 114a6d65992..184c8f88334 100644 --- a/src/abstract-interpretation/data-frame/mappers/assignment-mapper.ts +++ b/src/abstract-interpretation/data-frame/mappers/assignment-mapper.ts @@ -7,6 +7,7 @@ import type { ParentInformation } from '../../../r-bridge/lang-4.x/ast/model/pro import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; import type { DataFrameAssignmentInfo } from '../absint-info'; import { isDataFrameArgument } from './arguments'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; /** * Maps a concrete data frame assignment to data frame assignment info containing the ids of the identifier and assigned expression. @@ -14,14 +15,16 @@ import { isDataFrameArgument } from './arguments'; * @param identifier - The R node of the variable identifier * @param expression - The R node of the assigned expression * @param dfg - The data flow graph for resolving the arguments + * @param ctx - The analysis context * @returns Data frame assignment info containing the IDs of the identifier and expression, or `undefined` if the node does not represent a data frame assignment */ export function mapDataFrameVariableAssignment( identifier: RSymbol | RString, expression: RNode, - dfg: DataflowGraph + dfg: DataflowGraph, + ctx: ReadOnlyFlowrAnalyzerContext ): DataFrameAssignmentInfo | undefined { - const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias }; + const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias, ctx }; if(!isDataFrameArgument(expression, resolveInfo)) { return; diff --git a/src/abstract-interpretation/data-frame/mappers/function-mapper.ts b/src/abstract-interpretation/data-frame/mappers/function-mapper.ts index 495c6667b1a..fdf632aaa6e 100644 --- a/src/abstract-interpretation/data-frame/mappers/function-mapper.ts +++ b/src/abstract-interpretation/data-frame/mappers/function-mapper.ts @@ -5,18 +5,37 @@ import { toUnnamedArgument } from '../../../dataflow/internal/process/functions/ import { findSource } from '../../../dataflow/internal/process/functions/call/built-in/built-in-source'; import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; import type { RNode } from '../../../r-bridge/lang-4.x/ast/model/model'; -import { type RFunctionArgument, EmptyArgument } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { EmptyArgument, type RFunctionArgument } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { ParentInformation } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate'; import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; -import { type RParseRequest, requestFromInput } from '../../../r-bridge/retriever'; +import { requestFromInput, type RParseRequest } from '../../../r-bridge/retriever'; import { assertUnreachable, isNotUndefined, isUndefined } from '../../../util/assert'; import { readLineByLineSync } from '../../../util/files'; import type { DataFrameExpressionInfo, DataFrameOperation } from '../absint-info'; import { DataFrameDomain } from '../dataframe-domain'; -import { resolveIdToArgName, resolveIdToArgValue, resolveIdToArgValueSymbolName, resolveIdToArgVectorLength, unescapeSpecialChars } from '../resolve-args'; +import { + resolveIdToArgName, + resolveIdToArgValue, + resolveIdToArgValueSymbolName, + resolveIdToArgVectorLength, + unescapeSpecialChars +} from '../resolve-args'; import type { ConstraintType } from '../semantics'; import { resolveIdToDataFrameShape } from '../shape-inference'; -import { type FunctionParameterLocation, escapeRegExp, filterValidNames, getArgumentValue, getEffectiveArgs, getFunctionArgument, getFunctionArguments, getUnresolvedSymbolsInExpression, hasCriticalArgument, isDataFrameArgument, isNamedArgument, isRNull } from './arguments'; +import { + escapeRegExp, + filterValidNames, + type FunctionParameterLocation, + getArgumentValue, + getEffectiveArgs, + getFunctionArgument, + getFunctionArguments, + getUnresolvedSymbolsInExpression, + hasCriticalArgument, + isDataFrameArgument, + isNamedArgument, + isRNull +} from './arguments'; /** * Represents the different types of data frames in R @@ -602,7 +621,7 @@ export function mapDataFrameFunctionCall( if(node.type !== RType.FunctionCall || !node.named) { return; } - const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias }; + const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias, ctx }; let operations: DataFrameOperation[] | undefined; if(isDataFrameFunction(node.functionName.content)) { diff --git a/src/abstract-interpretation/data-frame/mappers/replacement-mapper.ts b/src/abstract-interpretation/data-frame/mappers/replacement-mapper.ts index a93ceab7e24..b600bf2f766 100644 --- a/src/abstract-interpretation/data-frame/mappers/replacement-mapper.ts +++ b/src/abstract-interpretation/data-frame/mappers/replacement-mapper.ts @@ -16,6 +16,7 @@ import { resolveIdToArgStringVector, resolveIdToArgValue, resolveIdToArgValueSym import { ConstraintType } from '../semantics'; import { isStringBasedAccess } from './access-mapper'; import { isDataFrameArgument, isRNull } from './arguments'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; /** Mapper for mapping the supported data frame replacement functions to mapper functions */ const DataFrameReplacementFunctionMapper = { @@ -46,15 +47,17 @@ type DataFrameReplacementFunction = keyof typeof DataFrameReplacementFunctionMap * Maps a concrete data frame replacement function to abstract data frame operations. * @param node - The R node of the replacement function * @param dfg - The data flow graph for resolving the arguments + * @param ctx - The read-only Flowr analysis context * @returns Data frame expression info containing the mapped abstract data frame operations, or `undefined` if the node does not represent a data frame replacement function */ export function mapDataFrameReplacementFunction( node: RNode, expression: RNode, - dfg: DataflowGraph + dfg: DataflowGraph, + ctx: ReadOnlyFlowrAnalyzerContext ): DataFrameExpressionInfo | undefined { const parent = hasParentReplacement(node, dfg) ? dfg.idMap?.get(node.info.parent) : undefined; - const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias }; + const resolveInfo = { graph: dfg, idMap: dfg.idMap, full: true, resolve: VariableResolve.Alias, ctx }; let operations: DataFrameOperation[] | undefined; if(node.type === RType.Access) { diff --git a/src/benchmark/slicer.ts b/src/benchmark/slicer.ts index 56648d5b77f..2f731baf3e5 100644 --- a/src/benchmark/slicer.ts +++ b/src/benchmark/slicer.ts @@ -3,7 +3,7 @@ * @module */ -import { type IStoppableStopwatch , Measurements } from './stopwatch'; +import { type IStoppableStopwatch, Measurements } from './stopwatch'; import seedrandom from 'seedrandom'; import { log, LogLevel } from '../util/log'; import type { MergeableRecord } from '../util/objects'; @@ -25,10 +25,18 @@ import type { } from './stats/stats'; import type { NormalizedAst, ParentInformation } from '../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { SlicingCriteria } from '../slicing/criterion/parse'; -import { type DEFAULT_SLICING_PIPELINE, type TREE_SITTER_SLICING_PIPELINE , createSlicePipeline } from '../core/steps/pipeline/default-pipelines'; -import { type RParseRequestFromFile, type RParseRequestFromText , retrieveNumberOfRTokensOfLastParse } from '../r-bridge/retriever'; +import { + createSlicePipeline, + type DEFAULT_SLICING_PIPELINE, + type TREE_SITTER_SLICING_PIPELINE +} from '../core/steps/pipeline/default-pipelines'; +import { + retrieveNumberOfRTokensOfLastParse, + type RParseRequestFromFile, + type RParseRequestFromText +} from '../r-bridge/retriever'; import type { PipelineStepNames, PipelineStepOutputWithName } from '../core/steps/pipeline/pipeline'; -import { type SlicingCriteriaFilter , collectAllSlicingCriteria } from '../slicing/criterion/collect-all'; +import { collectAllSlicingCriteria, type SlicingCriteriaFilter } from '../slicing/criterion/collect-all'; import { RType } from '../r-bridge/lang-4.x/ast/model/type'; import { visitAst } from '../r-bridge/lang-4.x/ast/model/processing/visitor'; import { getSizeOfDfGraph, safeSizeOf } from './stats/size-of'; @@ -39,9 +47,9 @@ import { RShell } from '../r-bridge/shell'; import { TreeSitterType } from '../r-bridge/lang-4.x/tree-sitter/tree-sitter-types'; import { TreeSitterExecutor } from '../r-bridge/lang-4.x/tree-sitter/tree-sitter-executor'; import type { InGraphIdentifierDefinition } from '../dataflow/environments/identifier'; -import { type ContainerIndicesCollection , isParentContainerIndex } from '../dataflow/graph/vertex'; +import { type ContainerIndicesCollection, isParentContainerIndex } from '../dataflow/graph/vertex'; import { equidistantSampling } from '../util/collections/arrays'; -import { type FlowrConfigOptions , getEngineConfig } from '../config'; +import { type FlowrConfigOptions, getEngineConfig } from '../config'; import type { ControlFlowInformation } from '../control-flow/control-flow-graph'; import { extractCfg } from '../control-flow/extract-cfg'; import type { RNode } from '../r-bridge/lang-4.x/ast/model/model'; diff --git a/src/control-flow/cfg-dead-code.ts b/src/control-flow/cfg-dead-code.ts index 0a4fbdef8f2..2f828699ee4 100644 --- a/src/control-flow/cfg-dead-code.ts +++ b/src/control-flow/cfg-dead-code.ts @@ -73,7 +73,8 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { graph: this.config.dfg, full: true, idMap: this.config.normalizedAst.idMap, - resolve: this.config.ctx.config.solver.variables + resolve: this.config.ctx.config.solver.variables, + ctx: this.config.ctx, })); if(values === undefined || values.elements.length !== 1 || values.elements[0].type != 'logical' || !isValue(values.elements[0].value)) { this.unableToCalculateValue(id); @@ -98,10 +99,10 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor { } const values = valueSetGuard(resolveIdToValue(data.call.args[0].nodeId, { - graph: this.config.dfg, - full: true, - idMap: this.config.normalizedAst.idMap, - resolve: this.config.ctx.config.solver.variables + graph: this.config.dfg, + full: true, + idMap: this.config.normalizedAst.idMap, + ctx: this.config.ctx, })); if(values === undefined || values.elements.length !== 1 || values.elements[0].type != 'logical' || !isValue(values.elements[0].value)) { return undefined; diff --git a/src/control-flow/useless-loop.ts b/src/control-flow/useless-loop.ts index 357fe0ecf48..72ad5c0b021 100644 --- a/src/control-flow/useless-loop.ts +++ b/src/control-flow/useless-loop.ts @@ -3,14 +3,14 @@ import { resolveIdToValue } from '../dataflow/eval/resolve/alias-tracking'; import { valueSetGuard } from '../dataflow/eval/values/general'; import { isValue } from '../dataflow/eval/values/r-value'; import type { DataflowGraph } from '../dataflow/graph/graph'; -import { type DataflowGraphVertexFunctionCall , VertexType } from '../dataflow/graph/vertex'; -import { type ControlDependency , happensInEveryBranch } from '../dataflow/info'; +import { type DataflowGraphVertexFunctionCall, VertexType } from '../dataflow/graph/vertex'; +import { type ControlDependency, happensInEveryBranch } from '../dataflow/info'; import { EmptyArgument } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { NormalizedAst } from '../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id'; import { guard } from '../util/assert'; import type { ControlFlowInformation } from './control-flow-graph'; -import { type SemanticCfgGuidedVisitorConfiguration , SemanticCfgGuidedVisitor } from './semantic-cfg-guided-visitor'; +import { SemanticCfgGuidedVisitor, type SemanticCfgGuidedVisitorConfiguration } from './semantic-cfg-guided-visitor'; import type { ReadOnlyFlowrAnalyzerContext } from '../project/context/flowr-analyzer-context'; @@ -48,7 +48,8 @@ export function onlyLoopsOnce(loop: NodeId, dataflow: DataflowGraph, controlflow const values = valueSetGuard(resolveIdToValue(vectorOfLoop.nodeId, { graph: dataflow, idMap: dataflow.idMap, - resolve: ctx.config.solver.variables + resolve: ctx.config.solver.variables, + ctx: ctx })); if(values === undefined || values.elements.length !== 1 || values.elements[0].type !== 'vector' || !isValue(values.elements[0].elements)) { return undefined; @@ -93,7 +94,8 @@ class CfgSingleIterationLoopDetector extends SemanticCfgGuidedVisitor { graph: this.config.dfg, full: true, idMap: this.config.normalizedAst.idMap, - resolve: this.config.ctx.config.solver.variables + resolve: this.config.ctx.config.solver.variables, + ctx: this.config.ctx })); if(values === undefined || values.elements.length !== 1 || values.elements[0].type != 'logical' || !isValue(values.elements[0].value)) { return undefined; diff --git a/src/core/steps/all/static-slicing/00-slice.ts b/src/core/steps/all/static-slicing/00-slice.ts index 710741bd880..603ecccf367 100644 --- a/src/core/steps/all/static-slicing/00-slice.ts +++ b/src/core/steps/all/static-slicing/00-slice.ts @@ -1,10 +1,11 @@ import { internalPrinter, StepOutputFormat } from '../../../print/print'; -import { type IPipelineStep , PipelineStepStage } from '../../pipeline-step'; +import { type IPipelineStep, PipelineStepStage } from '../../pipeline-step'; import type { DeepReadonly } from 'ts-essentials'; import type { DataflowInformation } from '../../../../dataflow/info'; import type { SlicingCriteria } from '../../../../slicing/criterion/parse'; import type { NormalizedAst } from '../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; import { staticSlice } from '../../../../slicing/static/static-slicer'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../../project/context/flowr-analyzer-context'; export interface SliceRequiredInput { /** The slicing criterion is only of interest if you actually want to slice the R code */ @@ -13,6 +14,8 @@ export interface SliceRequiredInput { readonly threshold?: number /** The direction to slice in. Defaults to backward slicing if unset. */ readonly direction?: SliceDirection + /** The context of the analysis */ + readonly context?: ReadOnlyFlowrAnalyzerContext } export enum SliceDirection { @@ -22,7 +25,8 @@ export enum SliceDirection { function processor(results: { dataflow?: DataflowInformation, normalize?: NormalizedAst }, input: Partial) { const direction = input.direction ?? SliceDirection.Backward; - return staticSlice((results.dataflow as DataflowInformation), results.normalize as NormalizedAst, input.criterion as SlicingCriteria, direction, input.threshold); + const threshold = input.threshold ?? input.context?.config.solver.slicer?.threshold; + return staticSlice(input.context as ReadOnlyFlowrAnalyzerContext, (results.dataflow as DataflowInformation), results.normalize as NormalizedAst, input.criterion as SlicingCriteria, direction, threshold); } export const STATIC_SLICE = { diff --git a/src/dataflow/environments/built-in.ts b/src/dataflow/environments/built-in.ts index 407411af5df..6421067d8d1 100644 --- a/src/dataflow/environments/built-in.ts +++ b/src/dataflow/environments/built-in.ts @@ -9,7 +9,7 @@ import { processPipe } from '../internal/process/functions/call/built-in/built-i import { processForLoop } from '../internal/process/functions/call/built-in/built-in-for-loop'; import { processRepeatLoop } from '../internal/process/functions/call/built-in/built-in-repeat-loop'; import { processWhileLoop } from '../internal/process/functions/call/built-in/built-in-while-loop'; -import { type Identifier, type IdentifierDefinition, type IdentifierReference , ReferenceType } from './identifier'; +import { type Identifier, type IdentifierDefinition, type IdentifierReference, ReferenceType } from './identifier'; import { guard } from '../../util/assert'; import { processReplacementFunction } from '../internal/process/functions/call/built-in/built-in-replacement'; import { processQuote } from '../internal/process/functions/call/built-in/built-in-quote'; @@ -17,7 +17,7 @@ import { processFunctionDefinition } from '../internal/process/functions/call/bu import { processExpressionList } from '../internal/process/functions/call/built-in/built-in-expression-list'; import { processGet } from '../internal/process/functions/call/built-in/built-in-get'; import type { AstIdMap, ParentInformation, RNodeWithParent } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type RFunctionArgument , EmptyArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { EmptyArgument, type RFunctionArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { RSymbol } from '../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { EdgeType } from '../graph/edge'; @@ -44,6 +44,7 @@ import type { BuiltInFunctionDefinition, BuiltInReplacementDefinition } from './built-in-config'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context'; export type BuiltIn = `built-in:${string}`; @@ -106,7 +107,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, ctx: ReadOnlyFlowrAnalyzerContext, env?: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap) => Value; function defaultBuiltInProcessor( name: RSymbol, diff --git a/src/dataflow/environments/environment.ts b/src/dataflow/environments/environment.ts index 8d8b30e9277..2c8cc6aa731 100644 --- a/src/dataflow/environments/environment.ts +++ b/src/dataflow/environments/environment.ts @@ -9,7 +9,6 @@ import type { DataflowGraph } from '../graph/graph'; import { resolveByName } from './resolve-by-name'; import type { ControlDependency } from '../info'; import { jsonReplacer } from '../../util/json'; -import { getDefaultBuiltInDefinitions } from './built-in-config'; import type { BuiltInMemory } from './built-in'; /** @@ -100,11 +99,6 @@ export class Environment implements IEnvironment { } } -export interface WorkingDirectoryReference { - readonly path: string - readonly controlDependencies: ControlDependency[] | undefined -} - /** * An environment describes a ({@link IEnvironment#parent|scoped}) mapping of names to their definitions ({@link BuiltIns}). * @@ -115,7 +109,7 @@ export interface WorkingDirectoryReference { * but statically determining all attached environments is theoretically impossible --- consider attachments by user input). * * One important environment is the {@link BuiltIns|BuiltInEnvironment} which contains the default definitions for R's built-in functions and constants. - * Please use {@link initializeCleanEnvironments} to initialize the environments (which includes the built-ins). + * This environment is created and provided by the {@link FlowrAnalyzerEnvironmentContext}. * During serialization, you may want to rely on the {@link builtInEnvJsonReplacer} to avoid the huge built-in environment. * @see {@link define} - to define a new {@link IdentifierDefinition|identifier definition} within an environment * @see {@link resolveByName} - to resolve an {@link Identifier|identifier/name} to its {@link IdentifierDefinition|definitions} within an environment @@ -132,19 +126,6 @@ export interface REnvironmentInformation { readonly level: number } -/** - * Initialize a new {@link REnvironmentInformation|environment} with the built-ins. - */ -export function initializeCleanEnvironments(memory?: BuiltInMemory, fullBuiltIns = true): REnvironmentInformation { - const builtInEnv = new Environment(undefined as unknown as IEnvironment, true); - builtInEnv.memory = memory ?? (fullBuiltIns ? getDefaultBuiltInDefinitions().builtInMemory : getDefaultBuiltInDefinitions().emptyBuiltInMemory); - - return { - current: new Environment(builtInEnv), - level: 0 - }; -} - /** * Helps to serialize an environment, but replaces the built-in environment with a placeholder. */ diff --git a/src/dataflow/environments/remove.ts b/src/dataflow/environments/remove.ts index ef56f4f9b7d..ddd18cef12c 100644 --- a/src/dataflow/environments/remove.ts +++ b/src/dataflow/environments/remove.ts @@ -3,11 +3,12 @@ import type { Identifier } from './identifier'; import { happensInEveryBranch } from '../info'; import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { cloneEnvironmentInformation } from './clone'; +import type { DeepReadonly } from 'ts-essentials'; /** * Removes all definitions of a given name from the environment. */ -export function remove(name: Identifier, environment: REnvironmentInformation, defaultEnvironment: IEnvironment): REnvironmentInformation { +export function remove(name: Identifier, environment: REnvironmentInformation, defaultEnvironment: DeepReadonly): REnvironmentInformation { let current: IEnvironment = environment.current; do{ const definition = current.memory.get(name); diff --git a/src/dataflow/eval/resolve/alias-tracking.ts b/src/dataflow/eval/resolve/alias-tracking.ts index 32e6a7f0eb1..bb45869d7a3 100644 --- a/src/dataflow/eval/resolve/alias-tracking.ts +++ b/src/dataflow/eval/resolve/alias-tracking.ts @@ -1,24 +1,24 @@ import { VariableResolve } from '../../../config'; import type { LinkTo } from '../../../queries/catalog/call-context-query/call-context-query-format'; import type { AstIdMap, RNodeWithParent } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type NodeId , recoverName } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id'; +import { type NodeId, recoverName } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id'; 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 IEnvironment, type REnvironmentInformation , initializeCleanEnvironments } from '../../environments/environment'; -import { type Identifier , ReferenceType } from '../../environments/identifier'; +import { type IEnvironment, type REnvironmentInformation } from '../../environments/environment'; +import { type Identifier, ReferenceType } from '../../environments/identifier'; import { resolveByName } from '../../environments/resolve-by-name'; import { EdgeType } from '../../graph/edge'; import type { DataflowGraph } from '../../graph/graph'; -import { type ReplacementOperatorHandlerArgs , onReplacementOperator } from '../../graph/unknown-replacement'; +import { onReplacementOperator, type ReplacementOperatorHandlerArgs } from '../../graph/unknown-replacement'; import { onUnknownSideEffect } from '../../graph/unknown-side-effect'; import { VertexType } from '../../graph/vertex'; import { valueFromRNodeConstant, valueFromTsValue } from '../values/general'; -import { type Lift, type Value, type ValueSet , Bottom, isTop, Top } from '../values/r-value'; +import { Bottom, isTop, type Lift, Top, type Value, type ValueSet } from '../values/r-value'; import { setFrom } from '../values/sets/set-constants'; import { resolveNode } from './resolve'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; export type ResolveResult = Lift>; @@ -41,7 +41,9 @@ export interface ResolveInfo { /** Whether to track variables */ full?: boolean; /** Variable resolve mode */ - resolve: VariableResolve; + resolve?: VariableResolve; + /** Context used for resolving */ + ctx: ReadOnlyFlowrAnalyzerContext; } function getFunctionCallAlias(sourceId: NodeId, dataflow: DataflowGraph, environment: REnvironmentInformation): NodeId[] | undefined { @@ -139,8 +141,11 @@ export function getAliases(sourceIds: readonly NodeId[], dataflow: DataflowGraph * @param idMap - The id map to resolve the node if given as an id * @param full - Whether to track aliases on resolve * @param resolve - Variable resolve mode + * @param ctx - Context used for clean environment */ -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, full = true, resolve, ctx }: ResolveInfo): ResolveResult { + const variableResolve = resolve ?? ctx.config.solver.variables; + if(id === undefined) { return Top; } @@ -155,9 +160,9 @@ export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { env case RType.Argument: case RType.Symbol: if(environment) { - return full ? trackAliasInEnvironments(resolve, node.lexeme, environment, graph, idMap) : Top; + return full ? trackAliasInEnvironments(variableResolve, node.lexeme, environment, ctx, graph, idMap) : Top; } else if(graph && resolve === VariableResolve.Alias) { - return full ? trackAliasesInGraph(node.info.id, graph, idMap) : Top; + return full ? trackAliasesInGraph(node.info.id, graph, ctx, idMap) : Top; } else { return Top; } @@ -166,7 +171,7 @@ export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { env case RType.FunctionCall: case RType.BinaryOp: case RType.UnaryOp: - return setFrom(resolveNode(resolve, node, environment, graph, idMap)); + return setFrom(resolveNode(variableResolve, node, ctx, environment, graph, idMap)); case RType.String: case RType.Number: case RType.Logical: @@ -184,11 +189,12 @@ export function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { env * @param resolve - Variable resolve mode * @param identifier - Identifier to resolve * @param use - Environment to use + * @param ctx - analysis context * @param graph - dataflow graph * @param idMap - id map of Dataflow graph * @returns Value of Identifier or Top */ -export function trackAliasInEnvironments(resolve: VariableResolve, identifier: Identifier | undefined, use: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ResolveResult { +export function trackAliasInEnvironments(resolve: VariableResolve, identifier: Identifier | undefined, use: REnvironmentInformation, ctx: ReadOnlyFlowrAnalyzerContext, graph?: DataflowGraph, idMap?: AstIdMap): ResolveResult { if(identifier === undefined) { return Top; } @@ -213,7 +219,7 @@ export function trackAliasInEnvironments(resolve: VariableResolve, identifier: I for(const alias of def.value) { const definitionOfAlias = idMap?.get(alias); if(definitionOfAlias !== undefined) { - const value = resolveNode(resolve, definitionOfAlias, use, graph, idMap); + const value = resolveNode(resolve, definitionOfAlias, ctx, use, graph, idMap); if(isTop(value)) { return Top; } @@ -295,11 +301,12 @@ function isNestedInLoop(node: RNodeWithParent | undefined, ast: AstIdMap): boole * * Tries to resolve the value of a node by traversing the dataflow graph * @param id - node to resolve + * @param ctx - analysis context * @param graph - dataflow graph * @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(id: NodeId, graph: DataflowGraph, ctx: ReadOnlyFlowrAnalyzerContext, idMap?: AstIdMap): ResolveResult { if(!graph.get(id)) { return Bottom; } @@ -308,8 +315,8 @@ export function trackAliasesInGraph(id: NodeId, graph: DataflowGraph, idMap?: As guard(idMap !== undefined, 'The ID map is required to get the lineage of a node'); const queue = new VisitingQueue(25); - const clean = initializeCleanEnvironments(); - const cleanFingerprint = envFingerprint(clean); + const clean = ctx.env.makeCleanEnv(); + const cleanFingerprint = ctx.env.getCleanEnvFingerprint(); queue.add(id, clean, cleanFingerprint, false); let forceTop = false; diff --git a/src/dataflow/eval/resolve/resolve-argument.ts b/src/dataflow/eval/resolve/resolve-argument.ts index 0634ca180da..07e29f9a013 100644 --- a/src/dataflow/eval/resolve/resolve-argument.ts +++ b/src/dataflow/eval/resolve/resolve-argument.ts @@ -1,4 +1,4 @@ -import { type DataflowGraph , getReferenceOfArgument } from '../../graph/graph'; +import { type DataflowGraph, getReferenceOfArgument } from '../../graph/graph'; import type { DataflowGraphVertexFunctionCall } from '../../graph/vertex'; import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { EmptyArgument } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; @@ -14,6 +14,7 @@ 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 type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; /** * Get the values of all arguments matching the criteria. @@ -24,7 +25,8 @@ export function getArgumentStringValue( vertex: DataflowGraphVertexFunctionCall, argumentIndex: number | 'unnamed' | undefined, argumentName: string | undefined, - resolveValue: boolean | 'library' | undefined + resolveValue: boolean | 'library' | undefined, + ctx: ReadOnlyFlowrAnalyzerContext ): Map> | undefined { if(argumentName) { const arg = vertex?.args.findIndex(arg => arg !== EmptyArgument && arg.name === argumentName); @@ -48,7 +50,7 @@ export function getArgumentStringValue( } if(valueNode) { // this should be evaluated in the callee-context - const values = resolveBasedOnConfig(variableResolve, graph, vertex, valueNode, vertex.environment, graph.idMap, resolveValue) ?? [Unknown]; + const values = resolveBasedOnConfig(variableResolve, graph, vertex, valueNode, vertex.environment, graph.idMap, resolveValue, ctx) ?? [Unknown]; map.set(ref, new Set(values)); } } @@ -65,7 +67,7 @@ export function getArgumentStringValue( } if(valueNode) { - const values = resolveBasedOnConfig(variableResolve, graph, vertex, valueNode, vertex.environment, graph.idMap, resolveValue) ?? [Unknown]; + const values = resolveBasedOnConfig(variableResolve, graph, vertex, valueNode, vertex.environment, graph.idMap, resolveValue, ctx) ?? [Unknown]; return new Map([[arg, new Set(values)]]); } } @@ -73,11 +75,11 @@ export function getArgumentStringValue( } -function hasCharacterOnly(variableResolve: VariableResolve, graph: DataflowGraph, vertex: DataflowGraphVertexFunctionCall, idMap: Map | undefined): boolean | 'maybe' { +function hasCharacterOnly(variableResolve: VariableResolve, graph: DataflowGraph, vertex: DataflowGraphVertexFunctionCall, idMap: Map | undefined, ctx: ReadOnlyFlowrAnalyzerContext): boolean | 'maybe' { if(!vertex.args || vertex.args.length === 0 || !idMap) { return false; } - const treatAsChar = getArgumentStringValue(variableResolve, graph, vertex, 5, 'character.only', true); + const treatAsChar = getArgumentStringValue(variableResolve, graph, vertex, 5, 'character.only', true, ctx); if(!treatAsChar) { return false; } @@ -90,14 +92,14 @@ 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 { +function resolveBasedOnConfig(variableResolve: VariableResolve, graph: DataflowGraph, vertex: DataflowGraphVertexFunctionCall, argument: RNodeWithParent, environment: REnvironmentInformation | undefined, idMap: Map | undefined, resolveValue: boolean | 'library' | undefined, ctx: ReadOnlyFlowrAnalyzerContext): string[] | undefined { let full = true; if(!resolveValue) { full = false; } if(resolveValue === 'library') { - const hasChar = hasCharacterOnly(variableResolve, graph, vertex, idMap); + const hasChar = hasCharacterOnly(variableResolve, graph, vertex, idMap, ctx); if(hasChar === false) { if(argument.type === RType.Symbol) { return [argument.lexeme]; @@ -106,7 +108,7 @@ function resolveBasedOnConfig(variableResolve: VariableResolve, graph: DataflowG } } - const resolved = valueSetGuard(resolveIdToValue(argument, { environment, graph, full, resolve: variableResolve })); + const resolved = valueSetGuard(resolveIdToValue(argument, { environment, graph, full, resolve: variableResolve, ctx })); if(resolved) { const values: string[] = []; for(const value of resolved.elements) { diff --git a/src/dataflow/eval/resolve/resolve.ts b/src/dataflow/eval/resolve/resolve.ts index c38565363b5..6166eb24d6d 100644 --- a/src/dataflow/eval/resolve/resolve.ts +++ b/src/dataflow/eval/resolve/resolve.ts @@ -9,12 +9,13 @@ import type { DataflowGraph } from '../../graph/graph'; import { getOriginInDfg, OriginType } from '../../origin/dfg-get-origin'; import { intervalFrom } from '../values/intervals/interval-constants'; import { ValueLogicalFalse, ValueLogicalTrue } from '../values/logical/logical-constants'; -import { type Lift, type Value, type ValueNumber, type ValueVector , Top } from '../values/r-value'; +import { type Lift, Top, type Value, type ValueNumber, type ValueVector } from '../values/r-value'; import { stringFrom } from '../values/string/string-constants'; import { flattenVectorElements, vectorFrom } from '../values/vectors/vector-constants'; import { resolveIdToValue } from './alias-tracking'; import type { VariableResolve } from '../../../config'; import { liftScalar } from '../values/scalar/scalar-consatnts'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; /** * Helper function used by {@link resolveIdToValue}, please use that instead, if @@ -29,7 +30,7 @@ import { liftScalar } from '../values/scalar/scalar-consatnts'; * @param map - Idmap of Dataflow Graph * @returns resolved value or top/bottom */ -export function resolveNode(resolve: VariableResolve, a: RNodeWithParent, env?: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap): Value { +export function resolveNode(resolve: VariableResolve, a: RNodeWithParent, ctx: ReadOnlyFlowrAnalyzerContext, env?: REnvironmentInformation, graph?: DataflowGraph, map?: AstIdMap): Value { if(a.type === RType.String) { return stringFrom(a.content.str); } else if(a.type === RType.Number) { @@ -56,7 +57,7 @@ export function resolveNode(resolve: VariableResolve, a: RNodeWithParent, env?: } if(Object.hasOwn(BuiltInEvalHandlerMapper, builtInName)) { const handler = BuiltInEvalHandlerMapper[builtInName as keyof typeof BuiltInEvalHandlerMapper]; - return handler(resolve, a, env, graph, map); + return handler(resolve, a, ctx, env, graph, map); } } return Top; @@ -68,18 +69,19 @@ export function resolveNode(resolve: VariableResolve, a: RNodeWithParent, env?: * * This function resolves a vector function call `c` to a {@link ValueVector} * by recursively resolving the values of the arguments by calling {@link resolveIdToValue} - * @param resolve - Variable resolve mode - * @param node - Node of the vector function to resolve - * @param env - Environment to use - * @param graph - Dataflow graph - * @param map - Id map of the dataflow graph + * @param resolve - Variable resolve mode + * @param node - Node of the vector function to resolve + * @param environment - Environment to use + * @param ctx - Analyzer context + * @param graph - Dataflow graph + * @param idMap - ID map of the dataflow graph * @returns ValueVector or Top */ -export function resolveAsVector(resolve: VariableResolve, node: RNodeWithParent, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueVector> | typeof Top { +export function resolveAsVector(resolve: VariableResolve, node: RNodeWithParent, ctx: ReadOnlyFlowrAnalyzerContext, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueVector> | typeof Top { if(node.type !== RType.FunctionCall) { return Top; } - const resolveInfo = { environment, graph, idMap, full: true, resolve }; + const resolveInfo = { environment, graph, idMap, full: true, resolve, ctx }; const values = node.arguments.map(arg => arg !== EmptyArgument ? resolveIdToValue(arg.value, resolveInfo) : Top); return vectorFrom(flattenVectorElements(values)); @@ -98,11 +100,11 @@ export function resolveAsVector(resolve: VariableResolve, node: RNodeWithParent, * @param map - Id map of the dataflow graph * @returns ValueVector of ValueNumbers or Top */ -export function resolveAsSeq(resolve: VariableResolve, operator: RNodeWithParent, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueVector> | typeof Top { +export function resolveAsSeq(resolve: VariableResolve, operator: RNodeWithParent, ctx: ReadOnlyFlowrAnalyzerContext, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueVector> | typeof Top { if(operator.type !== RType.BinaryOp) { return Top; } - const resolveInfo = { environment, graph, idMap, full: true, resolve }; + const resolveInfo = { environment, graph, idMap, full: true, resolve, ctx }; const leftArg = resolveIdToValue(operator.lhs, resolveInfo); const rightArg = resolveIdToValue(operator.rhs, resolveInfo); const leftValue = unliftRValue(leftArg); @@ -127,11 +129,11 @@ export function resolveAsSeq(resolve: VariableResolve, operator: RNodeWithParent * @param map - Id map of the dataflow graph * @returns ValueNumber, ValueVector of ValueNumbers, or Top */ -export function resolveAsPlus(resolve: VariableResolve, operator: RNodeWithParent, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueNumber | ValueVector> | typeof Top { +export function resolveAsPlus(resolve: VariableResolve, operator: RNodeWithParent, ctx: ReadOnlyFlowrAnalyzerContext, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueNumber | ValueVector> | typeof Top { if(operator.type !== RType.UnaryOp) { return Top; } - const resolveInfo = { environment, graph, idMap, full: true, resolve }; + const resolveInfo = { environment, graph, idMap, full: true, resolve, ctx }; const arg = resolveIdToValue(operator.operand, resolveInfo); const argValue = unliftRValue(arg); @@ -156,11 +158,11 @@ export function resolveAsPlus(resolve: VariableResolve, operator: RNodeWithParen * @param map - Id map of the dataflow graph * @returns ValueNumber, ValueVector of ValueNumbers, or Top */ -export function resolveAsMinus(resolve: VariableResolve, operator: RNodeWithParent, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueNumber | ValueVector> | typeof Top { +export function resolveAsMinus(resolve: VariableResolve, operator: RNodeWithParent, ctx: ReadOnlyFlowrAnalyzerContext, environment?: REnvironmentInformation, graph?: DataflowGraph, idMap?: AstIdMap): ValueNumber | ValueVector> | typeof Top { if(operator.type !== RType.UnaryOp) { return Top; } - const resolveInfo = { environment, graph, idMap, full: true, resolve }; + const resolveInfo = { environment, graph, idMap, full: true, resolve, ctx }; const arg = resolveIdToValue(operator.operand, resolveInfo); const argValue = unliftRValue(arg); diff --git a/src/dataflow/extractor.ts b/src/dataflow/extractor.ts index cea4f79303b..767a5f2e92d 100644 --- a/src/dataflow/extractor.ts +++ b/src/dataflow/extractor.ts @@ -1,5 +1,5 @@ import type { DataflowInformation } from './info'; -import { type DataflowProcessorInformation, type DataflowProcessors , processDataflowFor } from './processor'; +import { type DataflowProcessorInformation, type DataflowProcessors, processDataflowFor } from './processor'; import { processUninterestingLeaf } from './internal/process/process-uninteresting-leaf'; import { processSymbol } from './internal/process/process-symbol'; import { processFunctionCall } from './internal/process/functions/call/default-call-handling'; @@ -12,7 +12,6 @@ import { wrapArgumentsUnnamed } from './internal/process/functions/call/argument import { rangeFrom } from '../util/range'; import type { NormalizedAst, ParentInformation } from '../r-bridge/lang-4.x/ast/model/processing/decorate'; import { RType } from '../r-bridge/lang-4.x/ast/model/type'; -import { initializeCleanEnvironments } from './environments/environment'; import { standaloneSourceFile } from './internal/process/functions/call/built-in/built-in-source'; import type { DataflowGraph } from './graph/graph'; import { extractCfgQuick, getCallsInCfg } from '../control-flow/extract-cfg'; @@ -23,7 +22,6 @@ import { import type { KnownParserType, Parser } from '../r-bridge/parser'; import { updateNestedFunctionCalls } from './internal/process/functions/call/built-in/built-in-function-definition'; import type { ControlFlowInformation } from '../control-flow/control-flow-graph'; -import { getBuiltInDefinitions } from './environments/built-in-config'; import type { FlowrAnalyzerContext } from '../project/context/flowr-analyzer-context'; import { FlowrFile } from '../project/context/flowr-file'; import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id'; @@ -109,9 +107,6 @@ export function produceDataFlowGraph( completeAst: NormalizedAst, ctx: FlowrAnalyzerContext ): DataflowInformation & { cfgQuick: ControlFlowInformation | undefined } { - const builtInsConfig = ctx.config.semantics.environment.overwriteBuiltIns; - const builtIns = getBuiltInDefinitions(builtInsConfig.definitions, builtInsConfig.loadDefaults); - const env = initializeCleanEnvironments(builtIns.builtInMemory); // we freeze the files here to avoid endless modifications during processing const files = completeAst.ast.files.slice(); @@ -121,8 +116,7 @@ export function produceDataFlowGraph( const dfData: DataflowProcessorInformation = { parser, completeAst, - environment: env, - builtInEnvironment: env.current.parent, + environment: ctx.env.makeCleanEnv(), processors, controlDependencies: undefined, referenceChain: [files[0].filePath], diff --git a/src/dataflow/fn/higher-order-function.ts b/src/dataflow/fn/higher-order-function.ts index 4124ff09d9a..67a860bcec5 100644 --- a/src/dataflow/fn/higher-order-function.ts +++ b/src/dataflow/fn/higher-order-function.ts @@ -1,12 +1,18 @@ import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import type { DataflowGraph } from '../graph/graph'; -import { type DataflowGraphVertexArgument, type DataflowGraphVertexFunctionDefinition , isFunctionCallVertex, isFunctionDefinitionVertex } from '../graph/vertex'; +import { + type DataflowGraphVertexArgument, + type DataflowGraphVertexFunctionDefinition, + isFunctionCallVertex, + isFunctionDefinitionVertex +} from '../graph/vertex'; import { isNotUndefined } from '../../util/assert'; import { edgeIncludesType, EdgeType } from '../graph/edge'; import { resolveIdToValue } from '../eval/resolve/alias-tracking'; import { VariableResolve } from '../../config'; import { EmptyArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import { valueSetGuard } from '../eval/values/general'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context'; function isAnyReturnAFunction(def: DataflowGraphVertexFunctionDefinition, graph: DataflowGraph): boolean { const workingQueue: DataflowGraphVertexArgument[] = def.exitPoints.map(d => graph.getVertex(d, true)).filter(isNotUndefined); @@ -33,7 +39,7 @@ function isAnyReturnAFunction(def: DataflowGraphVertexFunctionDefinition, graph: return false; } -function inspectCallSitesArgumentsFns(def: DataflowGraphVertexFunctionDefinition, graph: DataflowGraph): boolean { +function inspectCallSitesArgumentsFns(def: DataflowGraphVertexFunctionDefinition, graph: DataflowGraph, ctx: ReadOnlyFlowrAnalyzerContext): boolean { const callSites = graph.ingoingEdges(def.id); for(const [callerId, { types }] of callSites ?? []) { @@ -48,7 +54,7 @@ function inspectCallSitesArgumentsFns(def: DataflowGraphVertexFunctionDefinition if(arg === EmptyArgument) { continue; } - const value = valueSetGuard(resolveIdToValue(arg.nodeId, { graph, idMap: graph.idMap, resolve: VariableResolve.Alias, full: true })); + const value = valueSetGuard(resolveIdToValue(arg.nodeId, { graph, idMap: graph.idMap, resolve: VariableResolve.Alias, full: true, ctx })); if(value?.elements.some(e => e.type === 'function-definition')) { return true; } @@ -63,7 +69,7 @@ function inspectCallSitesArgumentsFns(def: DataflowGraphVertexFunctionDefinition * If the return is an identity, e.g., `function(x) x`, this is not considered higher-order, * if no function is passed as an argument. */ -export function isHigherOrder(id: NodeId, graph: DataflowGraph): boolean { +export function isHigherOrder(id: NodeId, graph: DataflowGraph, ctx: ReadOnlyFlowrAnalyzerContext): boolean { const vert = graph.getVertex(id); if(!vert || !isFunctionDefinitionVertex(vert)) { return false; @@ -75,5 +81,5 @@ export function isHigherOrder(id: NodeId, graph: DataflowGraph): boolean { } // 2. check whether any of the callsites passes a function - return inspectCallSitesArgumentsFns(vert, graph); + return inspectCallSitesArgumentsFns(vert, graph, ctx); } \ No newline at end of file diff --git a/src/dataflow/graph/dataflowgraph-builder.ts b/src/dataflow/graph/dataflowgraph-builder.ts index 0cda1a79f3e..72a4f4a2c45 100644 --- a/src/dataflow/graph/dataflowgraph-builder.ts +++ b/src/dataflow/graph/dataflowgraph-builder.ts @@ -1,9 +1,20 @@ import { deepMergeObject } from '../../util/objects'; -import { type NodeId , normalizeIdToNumberIfPossible } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; +import { type NodeId, normalizeIdToNumberIfPossible } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import type { AstIdMap } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type DataflowFunctionFlowInformation, type FunctionArgument , DataflowGraph, isPositionalArgument } from './graph'; -import { type IEnvironment, type REnvironmentInformation , initializeCleanEnvironments } from '../environments/environment'; -import { type DataflowGraphVertexAstLink, type DataflowGraphVertexUse, type FunctionOriginInformation , VertexType } from './vertex'; +import { + type DataflowFunctionFlowInformation, + DataflowGraph, + type FunctionArgument, + isPositionalArgument +} from './graph'; +import { type IEnvironment, type REnvironmentInformation } from '../environments/environment'; +import { + type DataflowGraphVertexArgument, + type DataflowGraphVertexAstLink, type DataflowGraphVertexInfo, + type DataflowGraphVertexUse, + type FunctionOriginInformation, + VertexType +} from './vertex'; import { EmptyArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import { isBuiltIn } from '../environments/built-in'; import { EdgeType } from './edge'; @@ -14,13 +25,14 @@ import type { FlowrSearchLike } from '../../search/flowr-search-builder'; import { runSearch } from '../../search/flowr-search-executor'; import { guard } from '../../util/assert'; import type { ReadonlyFlowrAnalysisProvider } from '../../project/flowr-analyzer'; - +import { contextFromInput } from '../../project/context/flowr-analyzer-context'; /** - * + * Creates an empty dataflow graph. + * Should only be used in tests and documentation. */ -export function emptyGraph(idMap?: AstIdMap) { - return new DataflowGraphBuilder(idMap); +export function emptyGraph(cleanEnv?: REnvironmentInformation, idMap?: AstIdMap) { + return new DataflowGraphBuilder(cleanEnv, idMap); } export type DataflowGraphEdgeTarget = NodeId | (readonly NodeId[]); @@ -30,7 +42,21 @@ export type DataflowGraphEdgeTarget = NodeId | (readonly NodeId[]); * easily and compactly add vertices and edges to a dataflow graph. Its usage thus * simplifies writing tests for dataflow graphs. */ -export class DataflowGraphBuilder extends DataflowGraph { +export class DataflowGraphBuilder< + Vertex extends DataflowGraphVertexInfo = DataflowGraphVertexInfo, +> extends DataflowGraph { + private readonly defaultEnvironment: REnvironmentInformation; + + constructor(cleanEnv?: REnvironmentInformation, idMap?: AstIdMap) { + super(idMap); + this.defaultEnvironment = cleanEnv ?? contextFromInput('').env.makeCleanEnv(); + } + + public addVertexWithDefaultEnv(vertex: DataflowGraphVertexArgument & Omit, asRoot = true, overwrite = false): this { + super.addVertex(vertex, this.defaultEnvironment, asRoot, overwrite); + return this; + } + /** * Adds a **vertex** for a **function definition** (V1). * @param id - AST node ID @@ -44,7 +70,7 @@ export class DataflowGraphBuilder extends DataflowGraph { exitPoints: readonly NodeId[], subflow: DataflowFunctionFlowInformation, info?: { environment?: REnvironmentInformation, builtInEnvironment?: IEnvironment, controlDependencies?: ControlDependency[] }, asRoot: boolean = true) { - return this.addVertex({ + return this.addVertexWithDefaultEnv({ tag: VertexType.FunctionDefinition, id: normalizeIdToNumberIfPossible(id), subflow: { @@ -57,7 +83,7 @@ export class DataflowGraphBuilder extends DataflowGraph { } as DataflowFunctionFlowInformation, exitPoints: exitPoints.map(normalizeIdToNumberIfPossible), cds: info?.controlDependencies?.map(c => ({ ...c, id: normalizeIdToNumberIfPossible(c.id) })), - environment: info?.environment + environment: info?.environment, }, asRoot); } @@ -83,12 +109,12 @@ export class DataflowGraphBuilder extends DataflowGraph { }, asRoot: boolean = true) { const onlyBuiltInAuto = info?.reads?.length === 1 && isBuiltIn(info?.reads[0]); - this.addVertex({ + this.addVertexWithDefaultEnv({ tag: VertexType.FunctionCall, id: normalizeIdToNumberIfPossible(id), name, args: args.map(a => a === EmptyArgument ? EmptyArgument : { ...a, nodeId: normalizeIdToNumberIfPossible(a.nodeId), controlDependencies: undefined }), - environment: (info?.onlyBuiltIn || onlyBuiltInAuto) ? undefined : info?.environment ?? initializeCleanEnvironments(), + environment: (info?.onlyBuiltIn || onlyBuiltInAuto) ? undefined : info?.environment ?? this.defaultEnvironment, cds: info?.controlDependencies?.map(c => ({ ...c, id: normalizeIdToNumberIfPossible(c.id) })), onlyBuiltin: info?.onlyBuiltIn ?? onlyBuiltInAuto ?? false, origin: info?.origin ?? [ getDefaultProcessor(name) ?? 'function' ], @@ -137,7 +163,7 @@ export class DataflowGraphBuilder extends DataflowGraph { */ public defineVariable(id: NodeId, name?: string, info?: { controlDependencies?: ControlDependency[], definedBy?: NodeId[]}, asRoot: boolean = true) { - this.addVertex({ + this.addVertexWithDefaultEnv({ tag: VertexType.VariableDefinition, id: normalizeIdToNumberIfPossible(id), name, @@ -160,7 +186,7 @@ export class DataflowGraphBuilder extends DataflowGraph { * (i.e., be a valid entry point) or is it nested (e.g., as part of a function definition) */ public use(id: NodeId, name?: string, info?: Partial, asRoot: boolean = true) { - return this.addVertex(deepMergeObject({ + return this.addVertexWithDefaultEnv(deepMergeObject({ tag: VertexType.Use, id: normalizeIdToNumberIfPossible(id), name, @@ -181,7 +207,7 @@ export class DataflowGraphBuilder extends DataflowGraph { * (i.e., be a valid entry point), or is it nested (e.g., as part of a function definition) */ public constant(id: NodeId, options?: { controlDependencies?: ControlDependency[] }, asRoot: boolean = true) { - return this.addVertex({ + return this.addVertexWithDefaultEnv({ tag: VertexType.Value, id: normalizeIdToNumberIfPossible(id), cds: options?.controlDependencies?.map(c => ({ ...c, id: normalizeIdToNumberIfPossible(c.id) })), diff --git a/src/dataflow/graph/graph.ts b/src/dataflow/graph/graph.ts index 646ffc327da..8183d48f70f 100644 --- a/src/dataflow/graph/graph.ts +++ b/src/dataflow/graph/graph.ts @@ -14,11 +14,7 @@ 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, - initializeCleanEnvironments, - type REnvironmentInformation -} from '../environments/environment'; +import { type IEnvironment, 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'; @@ -138,8 +134,7 @@ export class DataflowGraph< Vertex extends DataflowGraphVertexInfo = DataflowGraphVertexInfo, Edge extends DataflowGraphEdge = DataflowGraphEdge > { - private static DEFAULT_ENVIRONMENT: REnvironmentInformation | undefined = undefined; - private _idMap: AstIdMap | undefined; + private _idMap: AstIdMap | undefined; /* * Set of vertices which have sideEffects that we do not know anything about. @@ -149,7 +144,6 @@ export class DataflowGraph< private readonly _unknownSideEffects = new Set(); constructor(idMap: AstIdMap | undefined) { - DataflowGraph.DEFAULT_ENVIRONMENT ??= initializeCleanEnvironments(); this._idMap = idMap; } @@ -283,19 +277,20 @@ export class DataflowGraph< /** * Adds a new vertex to the graph, for ease of use, some arguments are optional and filled automatically. * @param vertex - The vertex to add + * @param fallbackEnv - A clean environment to use if no environment is given in the vertex * @param asRoot - If false, this will only add the vertex but do not add it to the {@link rootIds|root vertices} of the graph. * This is probably only of use, when you construct dataflow graphs for tests. * @param overwrite - If true, this will overwrite the vertex if it already exists in the graph (based on the id). * @see DataflowGraphVertexInfo * @see DataflowGraphVertexArgument */ - public addVertex(vertex: DataflowGraphVertexArgument & Omit, asRoot = true, overwrite = false): this { + public addVertex(vertex: DataflowGraphVertexArgument & Omit, fallbackEnv: REnvironmentInformation, asRoot = true, overwrite = false): this { const oldVertex = this.vertexInformation.get(vertex.id); if(oldVertex !== undefined && !overwrite) { return this; } - const fallback = vertex.tag === VertexType.VariableDefinition || vertex.tag === VertexType.Use || vertex.tag === VertexType.Value || (vertex.tag === VertexType.FunctionCall && vertex.onlyBuiltin) ? undefined : DataflowGraph.DEFAULT_ENVIRONMENT; + const fallback = vertex.tag === VertexType.VariableDefinition || vertex.tag === VertexType.Use || vertex.tag === VertexType.Value || (vertex.tag === VertexType.FunctionCall && vertex.onlyBuiltin) ? undefined : fallbackEnv; // keep a clone of the original environment const environment = vertex.environment ? cloneEnvironmentInformation(vertex.environment) : fallback; diff --git a/src/dataflow/graph/invert-dfg.ts b/src/dataflow/graph/invert-dfg.ts index 4630f724bd9..830d59eb510 100644 --- a/src/dataflow/graph/invert-dfg.ts +++ b/src/dataflow/graph/invert-dfg.ts @@ -1,13 +1,14 @@ import { DataflowGraph } from './graph'; +import type { REnvironmentInformation } from '../environments/environment'; /** - * + * Inverts the given dataflow graph by reversing all edges. */ -export function invertDfg(graph: DataflowGraph): DataflowGraph { +export function invertDfg(graph: DataflowGraph, cleanEnv: REnvironmentInformation): DataflowGraph { const invertedGraph = new DataflowGraph(graph.idMap); for(const [,v] of graph.vertices(true)) { - invertedGraph.addVertex(v); + invertedGraph.addVertex(v, cleanEnv); } for(const [from, targets] of graph.edges()) { for(const [to, { types }] of targets) { diff --git a/src/dataflow/graph/resolve-graph.ts b/src/dataflow/graph/resolve-graph.ts index 9851fa34ad9..d59abcc6dab 100644 --- a/src/dataflow/graph/resolve-graph.ts +++ b/src/dataflow/graph/resolve-graph.ts @@ -2,14 +2,15 @@ import { DataflowGraph } from './graph'; import { type AstIdMap } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { guard } from '../../util/assert'; -import { type SingleSlicingCriterion , slicingCriterionToId } from '../../slicing/criterion/parse'; +import { type SingleSlicingCriterion, slicingCriterionToId } from '../../slicing/criterion/parse'; import { splitEdgeTypes } from './edge'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context'; /** * Resolves the dataflow graph ids from slicing criterion form to ids. * This returns a **new** graph with the resolved ids. */ -export function resolveDataflowGraph(graph: DataflowGraph, idMap?: AstIdMap): DataflowGraph { +export function resolveDataflowGraph(graph: DataflowGraph, ctx: ReadOnlyFlowrAnalyzerContext, idMap?: AstIdMap): DataflowGraph { const resolveMap = idMap ?? graph.idMap; guard(resolveMap !== undefined, 'idMap must be provided to resolve the graph'); @@ -40,7 +41,7 @@ export function resolveDataflowGraph(graph: DataflowGraph, idMap?: AstIdMap): Da resultGraph.addVertex({ ...vertex, id: resolve(id as string) - }, roots.has(id)); + }, ctx.env.makeCleanEnv(), roots.has(id)); } /* recreate edges */ for(const [from, targets] of graph.edges()) { diff --git a/src/dataflow/info.ts b/src/dataflow/info.ts index c64b5326798..263f08906a4 100644 --- a/src/dataflow/info.ts +++ b/src/dataflow/info.ts @@ -113,7 +113,7 @@ export interface DataflowInformation extends DataflowCfgInformation { * This is to be used as a "starting point" when processing leaf nodes during the dataflow extraction. * @see {@link DataflowInformation} */ -export function initializeCleanDataflowInformation(entryPoint: NodeId, data: Pick, 'environment' | 'builtInEnvironment' | 'completeAst'>): DataflowInformation { +export function initializeCleanDataflowInformation(entryPoint: NodeId, data: Pick, 'environment' | 'completeAst'>): DataflowInformation { return { unknownReferences: [], in: [], diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-apply.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-apply.ts index 378c3b628c7..7fe8b136b74 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-apply.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-apply.ts @@ -1,7 +1,10 @@ import type { DataflowProcessorInformation } from '../../../../../processor'; import type { DataflowInformation } from '../../../../../info'; import { processKnownFunctionCall } from '../known-call-handling'; -import { type RFunctionArgument , EmptyArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { + EmptyArgument, + type RFunctionArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; @@ -11,7 +14,7 @@ import { RType } from '../../../../../../r-bridge/lang-4.x/ast/model/type'; import { VertexType } from '../../../../../graph/vertex'; import type { FunctionArgument } from '../../../../../graph/graph'; import { EdgeType } from '../../../../../graph/edge'; -import { type IdentifierReference , isReferenceType, ReferenceType } from '../../../../../environments/identifier'; +import { type IdentifierReference, isReferenceType, ReferenceType } from '../../../../../environments/identifier'; import { resolveByName } from '../../../../../environments/resolve-by-name'; import { UnnamedFunctionCallPrefix } from '../unnamed-call-handling'; import { valueSetGuard } from '../../../../../eval/values/general'; @@ -91,7 +94,7 @@ export function processApply( } else if(val.type === RType.Symbol) { functionId = val.info.id; if(resolveValue) { - const resolved = valueSetGuard(resolveIdToValue(val.info.id, { environment: data.environment, idMap: data.completeAst.idMap , resolve: data.ctx.config.solver.variables })); + const resolved = valueSetGuard(resolveIdToValue(val.info.id, { environment: data.environment, idMap: data.completeAst.idMap , resolve: data.ctx.config.solver.variables, ctx: data.ctx })); if(resolved?.elements.length === 1 && resolved.elements[0].type === 'string') { functionName = isValue(resolved.elements[0].value) ? resolved.elements[0].value.str : undefined; } @@ -136,7 +139,7 @@ export function processApply( cds: data.controlDependencies, args: allOtherArguments, // same reference origin: ['function'] - }); + }, data.ctx.env.makeCleanEnv()); information.graph.addEdge(rootId, rootFnId, EdgeType.Calls | EdgeType.Reads); information.graph.addEdge(rootId, functionId, EdgeType.Calls | EdgeType.Argument); information = { diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-assignment.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-assignment.ts index 8454c7241c5..85c926e41ce 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-assignment.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-assignment.ts @@ -21,13 +21,14 @@ import { dataflowLogger } from '../../../../../logger'; import { type IdentifierReference, type InGraphIdentifierDefinition, - type InGraphReferenceType - , ReferenceType } from '../../../../../environments/identifier'; + type InGraphReferenceType, + ReferenceType +} from '../../../../../environments/identifier'; import { overwriteEnvironment } from '../../../../../environments/overwrite'; import type { RString } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-string'; import { removeRQuotes } from '../../../../../../r-bridge/retriever'; import type { RUnnamedArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-argument'; -import { type ContainerIndicesCollection , VertexType } from '../../../../../graph/vertex'; +import { type ContainerIndicesCollection, VertexType } from '../../../../../graph/vertex'; import { define } from '../../../../../environments/define'; import { EdgeType } from '../../../../../graph/edge'; import type { ForceArguments } from '../common'; @@ -173,7 +174,7 @@ export function processAssignment( }); } else { // try to resolve the variable first - const n = resolveIdToValue(target.info.id, { environment: data.environment, resolve: data.ctx.config.solver.variables, idMap: data.completeAst.idMap, full: true }); + const n = resolveIdToValue(target.info.id, { environment: data.environment, resolve: data.ctx.config.solver.variables, idMap: data.completeAst.idMap, full: true, ctx: data.ctx }); if(n.type === 'set' && n.elements.length === 1 && n.elements[0].type === 'string') { const val = n.elements[0].value; if(isValue(val)) { diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-eval.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-eval.ts index d578ebd7047..29618a7c20b 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-eval.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-eval.ts @@ -1,11 +1,16 @@ import { type DataflowProcessorInformation } from '../../../../../processor'; -import { type DataflowInformation , initializeCleanDataflowInformation } from '../../../../../info'; +import { type DataflowInformation, initializeCleanDataflowInformation } from '../../../../../info'; import { processKnownFunctionCall } from '../known-call-handling'; import { requestFromInput } from '../../../../../../r-bridge/retriever'; -import { type AstIdMap, type ParentInformation , +import { + type AstIdMap, + type ParentInformation, sourcedDeterministicCountingIdGenerator } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type RFunctionArgument , EmptyArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { + EmptyArgument, + type RFunctionArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { dataflowLogger } from '../../../../../logger'; @@ -24,6 +29,7 @@ import { handleUnknownSideEffect } from '../../../../../graph/unknown-side-effec import { resolveIdToValue } from '../../../../../eval/resolve/alias-tracking'; import { cartesianProduct } from '../../../../../../util/collections/arrays'; import type { FlowrConfigOptions } from '../../../../../../config'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../../../../project/context/flowr-analyzer-context'; /** @@ -63,7 +69,7 @@ export function processEvalCall( return information; } - const code: string[] | undefined = resolveEvalToCode(evalArgument.value as RNode, data.environment, data.completeAst.idMap, data.ctx.config); + const code: string[] | undefined = resolveEvalToCode(evalArgument.value as RNode, data.environment, data.completeAst.idMap, data.ctx); if(code) { const idGenerator = sourcedDeterministicCountingIdGenerator(name.lexeme + '::' + rootId, name.location); @@ -100,7 +106,7 @@ export function processEvalCall( return information; } -function resolveEvalToCode(evalArgument: RNode, env: REnvironmentInformation, idMap: AstIdMap, config: FlowrConfigOptions): string[] | undefined { +function resolveEvalToCode(evalArgument: RNode, env: REnvironmentInformation, idMap: AstIdMap, ctx: ReadOnlyFlowrAnalyzerContext): string[] | undefined { const val = evalArgument; if( @@ -114,12 +120,12 @@ function resolveEvalToCode(evalArgument: RNode(evalArgument: RNode | undefined, env: REnvironmentInformation, idMap: AstIdMap): string[] | undefined { +function getAsString(config: FlowrConfigOptions, val: RNode | undefined, env: REnvironmentInformation, idMap: AstIdMap, ctx: ReadOnlyFlowrAnalyzerContext): string[] | undefined { if(!val) { return undefined; } if(val.type === RType.String) { return [val.content.str]; } else if(val.type === RType.Symbol) { - const resolved = valueSetGuard(resolveIdToValue(val.info.id, { environment: env, idMap: idMap, resolve: config.solver.variables })); + const resolved = valueSetGuard(resolveIdToValue(val.info.id, { environment: env, idMap: idMap, resolve: config.solver.variables, ctx })); if(resolved) { return collectStrings(resolved.elements); } @@ -146,10 +152,10 @@ function getAsString(config: FlowrConfigOptions, val: RNode | return undefined; } -function handlePaste(config: FlowrConfigOptions, args: readonly RFunctionArgument[], env: REnvironmentInformation, idMap: AstIdMap, sepDefault: string[]): string[] | undefined { +function handlePaste(config: FlowrConfigOptions, args: readonly RFunctionArgument[], env: REnvironmentInformation, idMap: AstIdMap, sepDefault: string[], ctx: ReadOnlyFlowrAnalyzerContext): string[] | undefined { const sepArg = args.find(v => v !== EmptyArgument && v.name?.content === 'sep'); if(sepArg) { - const res = sepArg !== EmptyArgument && sepArg.value ? getAsString(config, sepArg.value, env, idMap) : undefined; + const res = sepArg !== EmptyArgument && sepArg.value ? getAsString(config, sepArg.value, env, idMap, ctx) : undefined; if(!res) { // sep not resolvable clearly / unknown return undefined; @@ -159,7 +165,7 @@ function handlePaste(config: FlowrConfigOptions, args: readonly RFunctionArgumen const allArgs = args .filter(v => v !== EmptyArgument && v.name?.content !== 'sep' && v.value) - .map(v => getAsString(config, (v as RArgument).value, env, idMap)); + .map(v => getAsString(config, (v as RArgument).value, env, idMap, ctx)); if(allArgs.some(isUndefined)) { return undefined; } diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-function-definition.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-function-definition.ts index 6fc502266c2..2996eca5796 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-function-definition.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-function-definition.ts @@ -1,5 +1,5 @@ -import { type DataflowProcessorInformation , processDataflowFor } from '../../../../../processor'; -import { type DataflowInformation , ExitPointType } from '../../../../../info'; +import { type DataflowProcessorInformation, processDataflowFor } from '../../../../../processor'; +import { type DataflowInformation, ExitPointType } from '../../../../../info'; import { getAllFunctionCallTargets, linkCircularRedefinitionsWithinALoop, @@ -12,18 +12,22 @@ import { guard } from '../../../../../../util/assert'; import { dataflowLogger } from '../../../../../logger'; import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; -import { type RFunctionArgument , EmptyArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { + EmptyArgument, + type RFunctionArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; -import { type DataflowFunctionFlowInformation , DataflowGraph } from '../../../../../graph/graph'; -import { type IdentifierReference , isReferenceType, ReferenceType } from '../../../../../environments/identifier'; +import { type DataflowFunctionFlowInformation, DataflowGraph } from '../../../../../graph/graph'; +import { type IdentifierReference, isReferenceType, ReferenceType } from '../../../../../environments/identifier'; import { overwriteEnvironment } from '../../../../../environments/overwrite'; import { VertexType } from '../../../../../graph/vertex'; import { popLocalEnvironment, pushLocalEnvironment } from '../../../../../environments/scoping'; -import { type REnvironmentInformation , initializeCleanEnvironments } from '../../../../../environments/environment'; +import { type REnvironmentInformation } from '../../../../../environments/environment'; import { resolveByName } from '../../../../../environments/resolve-by-name'; import { EdgeType } from '../../../../../graph/edge'; import { expensiveTrace } from '../../../../../../util/log'; import { isBuiltIn } from '../../../../../environments/built-in'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../../../../project/context/flowr-analyzer-context'; /** * Process a function definition, i.e., `function(a, b) { ... }` @@ -98,7 +102,7 @@ export function processFunctionDefinition( id: read.nodeId, environment: undefined, cds: undefined - }); + }, data.ctx.env.makeCleanEnv()); } } @@ -122,7 +126,7 @@ export function processFunctionDefinition( cds: data.controlDependencies, subflow: flow, exitPoints: exitPoints?.filter(e => e.type === ExitPointType.Return || e.type === ExitPointType.Default).map(e => e.nodeId) ?? [] - }); + }, data.ctx.env.makeCleanEnv()); return { /* nothing escapes a function definition, but the function itself, will be forced in assignment: { nodeId: functionDefinition.info.id, scope: data.activeScope, used: 'always', name: functionDefinition.info.id as string } */ unknownReferences: [], @@ -141,8 +145,8 @@ export function processFunctionDefinition( /** * */ -export function retrieveActiveEnvironment(callerEnvironment: REnvironmentInformation | undefined, baseEnvironment: REnvironmentInformation): REnvironmentInformation { - callerEnvironment ??= initializeCleanEnvironments(undefined, true); +export function retrieveActiveEnvironment(callerEnvironment: REnvironmentInformation | undefined, baseEnvironment: REnvironmentInformation, ctx: ReadOnlyFlowrAnalyzerContext): REnvironmentInformation { + callerEnvironment ??= ctx.env.makeCleanEnv(); let level = callerEnvironment.level ?? 0; if(baseEnvironment.level !== level) { @@ -264,7 +268,7 @@ export function updateNestedFunctionCalls( } function prepareFunctionEnvironment(data: DataflowProcessorInformation) { - let env = initializeCleanEnvironments(data.builtInEnvironment.memory); + let env = data.ctx.env.makeCleanEnv(); for(let i = 0; i < data.environment.level + 1 /* add another env */; i++) { env = pushLocalEnvironment(env); } diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.ts index 59798441c4a..41df97db49f 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.ts @@ -1,5 +1,5 @@ -import { type DataflowProcessorInformation , processDataflowFor } from '../../../../../processor'; -import { type DataflowInformation , alwaysExits } from '../../../../../info'; +import { type DataflowProcessorInformation, processDataflowFor } from '../../../../../processor'; +import { alwaysExits, type DataflowInformation } from '../../../../../info'; import { processKnownFunctionCall } from '../known-call-handling'; import { patchFunctionCall } from '../common'; import { unpackArgument } from '../argument/unpack-argument'; @@ -10,8 +10,8 @@ import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/proce import { dataflowLogger } from '../../../../../logger'; import { EdgeType } from '../../../../../graph/edge'; import { appendEnvironment } from '../../../../../environments/append'; -import { type IdentifierReference , ReferenceType } from '../../../../../environments/identifier'; -import { type REnvironmentInformation , makeAllMaybe } from '../../../../../environments/environment'; +import { type IdentifierReference, ReferenceType } from '../../../../../environments/identifier'; +import { makeAllMaybe, type REnvironmentInformation } from '../../../../../environments/environment'; import { valueSetGuard } from '../../../../../eval/values/general'; import { resolveIdToValue } from '../../../../../eval/resolve/alias-tracking'; @@ -52,7 +52,7 @@ export function processIfThenElse( let makeThenMaybe = false; // we should defer this to the abstract interpretation - const values = resolveIdToValue(condArg?.info.id, { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables }); + const values = resolveIdToValue(condArg?.info.id, { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables, ctx: data.ctx }); const conditionIsAlwaysFalse = valueSetGuard(values)?.elements.every(d => d.type === 'logical' && d.value === false) ?? false; const conditionIsAlwaysTrue = valueSetGuard(values)?.elements.every(d => d.type === 'logical' && d.value === true) ?? false; diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-rm.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-rm.ts index 7bd879da381..842872a58f8 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-rm.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-rm.ts @@ -2,7 +2,10 @@ import type { DataflowProcessorInformation } from '../../../../../processor'; import type { DataflowInformation } from '../../../../../info'; import { processKnownFunctionCall } from '../known-call-handling'; import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type RFunctionArgument , EmptyArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { + EmptyArgument, + type RFunctionArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { dataflowLogger } from '../../../../../logger'; @@ -44,7 +47,7 @@ export function processRm( let env = res.environment; for(const name of names) { - env = remove(name, env, data.builtInEnvironment); + env = remove(name, env, data.ctx.env.builtInEnvironment); } return { diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-source.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-source.ts index 8bd02e3874f..ef29ff0848d 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-source.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-source.ts @@ -1,20 +1,18 @@ import { type DataflowProcessorInformation, processDataflowFor } from '../../../../../processor'; -import { type DataflowInformation , initializeCleanDataflowInformation } from '../../../../../info'; -import { type FlowrLaxSourcingOptions , DropPathsOption, InferWorkingDirectory } from '../../../../../../config'; +import { type DataflowInformation, initializeCleanDataflowInformation } from '../../../../../info'; +import { DropPathsOption, type FlowrLaxSourcingOptions, InferWorkingDirectory } from '../../../../../../config'; import { processKnownFunctionCall } from '../known-call-handling'; -import { - type RParseRequestFromText, - type RParseRequest, - removeRQuotes -} from '../../../../../../r-bridge/retriever'; +import { removeRQuotes, type RParseRequest, type RParseRequestFromText } from '../../../../../../r-bridge/retriever'; import { type IdGenerator, type NormalizedAst, - type ParentInformation - , + type ParentInformation, sourcedDeterministicCountingIdGenerator } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type RFunctionArgument , EmptyArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { + EmptyArgument, + type RFunctionArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { dataflowLogger } from '../../../../../logger'; @@ -30,9 +28,7 @@ import { valueSetGuard } from '../../../../../eval/values/general'; import { isValue } from '../../../../../eval/values/r-value'; import { handleUnknownSideEffect } from '../../../../../graph/unknown-side-effect'; import { resolveIdToValue } from '../../../../../eval/resolve/alias-tracking'; -import type { - ReadOnlyFlowrAnalyzerContext -} from '../../../../../../project/context/flowr-analyzer-context'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../../../../project/context/flowr-analyzer-context'; import type { RProjectFile } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-project'; /** @@ -186,7 +182,7 @@ export function processSourceCall( if(sourceFileArgument !== EmptyArgument && sourceFileArgument?.value?.type === RType.String) { sourceFile = [removeRQuotes(sourceFileArgument.lexeme)]; } else if(sourceFileArgument !== EmptyArgument) { - const resolved = valueSetGuard(resolveIdToValue(sourceFileArgument.info.id, { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables })); + const resolved = valueSetGuard(resolveIdToValue(sourceFileArgument.info.id, { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables, ctx: data.ctx })); sourceFile = resolved?.elements.map(r => r.type === 'string' && isValue(r.value) ? r.value.str : undefined).filter(isNotUndefined); } diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-while-loop.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-while-loop.ts index 634620c6f4e..beaf40a3cd9 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-while-loop.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-while-loop.ts @@ -1,16 +1,20 @@ import type { DataflowProcessorInformation } from '../../../../../processor'; -import { type DataflowInformation , alwaysExits, filterOutLoopExitPoints } from '../../../../../info'; +import { alwaysExits, type DataflowInformation, filterOutLoopExitPoints } from '../../../../../info'; import { findNonLocalReads, linkCircularRedefinitionsWithinALoop, linkInputs, - produceNameSharedIdMap, reapplyLoopExitPoints + produceNameSharedIdMap, + reapplyLoopExitPoints } from '../../../../linker'; import { processKnownFunctionCall } from '../known-call-handling'; import { guard, isUndefined } from '../../../../../../util/assert'; import { unpackArgument } from '../argument/unpack-argument'; import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type RFunctionArgument , EmptyArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { + EmptyArgument, + type RFunctionArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { dataflowLogger } from '../../../../../logger'; @@ -44,7 +48,7 @@ export function processWhileLoop( } // we should defer this to the abstract interpretation - const values = resolveIdToValue(unpackedArgs[0]?.info.id, { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables }); + const values = resolveIdToValue(unpackedArgs[0]?.info.id, { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables, ctx: data.ctx }); const conditionIsAlwaysFalse = valueSetGuard(values)?.elements.every(d => d.type === 'logical' && d.value === false) ?? false; //We don't care about the body if it never executes diff --git a/src/dataflow/internal/process/functions/call/common.ts b/src/dataflow/internal/process/functions/call/common.ts index fe75b1638f2..a20e60ea222 100644 --- a/src/dataflow/internal/process/functions/call/common.ts +++ b/src/dataflow/internal/process/functions/call/common.ts @@ -1,12 +1,17 @@ -import { type DataflowInformation , happensInEveryBranch } from '../../../../info'; -import { type DataflowProcessorInformation , processDataflowFor } from '../../../../processor'; +import { type DataflowInformation, happensInEveryBranch } from '../../../../info'; +import { type DataflowProcessorInformation, processDataflowFor } from '../../../../processor'; import type { RNode } from '../../../../../r-bridge/lang-4.x/ast/model/model'; import type { ParentInformation } from '../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type RFunctionArgument , EmptyArgument } from '../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { EmptyArgument, type RFunctionArgument } from '../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { DataflowGraph, FunctionArgument } from '../../../../graph/graph'; import type { NodeId } from '../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import type { REnvironmentInformation } from '../../../../environments/environment'; -import { type IdentifierReference, type InGraphIdentifierDefinition , isReferenceType, ReferenceType } from '../../../../environments/identifier'; +import { + type IdentifierReference, + type InGraphIdentifierDefinition, + isReferenceType, + ReferenceType +} from '../../../../environments/identifier'; import { overwriteEnvironment } from '../../../../environments/overwrite'; import { resolveByName } from '../../../../environments/resolve-by-name'; import { RType } from '../../../../../r-bridge/lang-4.x/ast/model/type'; @@ -14,8 +19,10 @@ import { type ContainerIndicesCollection, type DataflowGraphVertexAstLink, type DataflowGraphVertexFunctionDefinition, - type FunctionOriginInformation - , isFunctionDefinitionVertex, VertexType } from '../../../../graph/vertex'; + type FunctionOriginInformation, + isFunctionDefinitionVertex, + VertexType +} from '../../../../graph/vertex'; import type { RSymbol } from '../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; import { EdgeType } from '../../../../graph/edge'; @@ -176,7 +183,7 @@ export function patchFunctionCall( args: argumentProcessResult.map(arg => arg === undefined ? EmptyArgument : { nodeId: arg.entryPoint, controlDependencies: undefined, call: undefined, type: ReferenceType.Argument }), origin: [origin], link - }, !nextGraph.hasVertex(rootId) || nextGraph.isRoot(rootId), true); + }, data.ctx.env.makeCleanEnv(), !nextGraph.hasVertex(rootId) || nextGraph.isRoot(rootId), true); for(const arg of argumentProcessResult) { if(arg) { nextGraph.addEdge(rootId, arg.entryPoint, EdgeType.Argument); diff --git a/src/dataflow/internal/process/functions/call/known-call-handling.ts b/src/dataflow/internal/process/functions/call/known-call-handling.ts index 0d9fbecc97a..ce152e71e1e 100644 --- a/src/dataflow/internal/process/functions/call/known-call-handling.ts +++ b/src/dataflow/internal/process/functions/call/known-call-handling.ts @@ -6,11 +6,11 @@ import type { ParentInformation } from '../../../../../r-bridge/lang-4.x/ast/mod import type { RFunctionArgument } from '../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import type { NodeId } from '../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import type { RNode } from '../../../../../r-bridge/lang-4.x/ast/model/model'; -import { type IdentifierReference , ReferenceType } from '../../../../environments/identifier'; +import { type IdentifierReference, ReferenceType } from '../../../../environments/identifier'; import { DataflowGraph } from '../../../../graph/graph'; import { EdgeType } from '../../../../graph/edge'; import { dataflowLogger } from '../../../../logger'; -import { type ContainerIndicesCollection, type FunctionOriginInformation , VertexType } from '../../../../graph/vertex'; +import { type ContainerIndicesCollection, type FunctionOriginInformation, VertexType } from '../../../../graph/vertex'; import { expensiveTrace } from '../../../../../util/log'; import { handleUnknownSideEffect } from '../../../../graph/unknown-side-effect'; @@ -93,7 +93,7 @@ export function processKnownFunctionCall( args: reverseOrder ? callArgs.reverse() : callArgs, indicesCollection: indicesCollection, origin: origin === 'default' ? ['function'] : [origin] - }); + }, data.ctx.env.makeCleanEnv()); if(hasUnknownSideEffect) { handleUnknownSideEffect(finalGraph, data.environment, rootId); diff --git a/src/dataflow/internal/process/functions/call/unnamed-call-handling.ts b/src/dataflow/internal/process/functions/call/unnamed-call-handling.ts index ce1333d0c7a..933a769792f 100644 --- a/src/dataflow/internal/process/functions/call/unnamed-call-handling.ts +++ b/src/dataflow/internal/process/functions/call/unnamed-call-handling.ts @@ -1,4 +1,4 @@ -import { type DataflowProcessorInformation , processDataflowFor } from '../../../../processor'; +import { type DataflowProcessorInformation, processDataflowFor } from '../../../../processor'; import type { DataflowInformation } from '../../../../info'; import { processAllArguments } from './common'; import { linkArgumentsOnCall } from '../../../linker'; @@ -54,7 +54,7 @@ export function processUnnamedFunctionCall(functionCall: RUnnamedFunc cds: data.controlDependencies, args: callArgs, // same reference origin: [UnnamedFunctionCallOrigin] - }); + }, data.ctx.env.makeCleanEnv()); let inIds = remainingReadInArgs; inIds.push({ nodeId: functionRootId, name: functionCallName, controlDependencies: data.controlDependencies, type: ReferenceType.Function }); diff --git a/src/dataflow/internal/process/functions/process-argument.ts b/src/dataflow/internal/process/functions/process-argument.ts index e7cce2cf87e..39d146ad265 100644 --- a/src/dataflow/internal/process/functions/process-argument.ts +++ b/src/dataflow/internal/process/functions/process-argument.ts @@ -1,5 +1,5 @@ -import { type DataflowProcessorInformation , processDataflowFor } from '../../../processor'; -import { type DataflowInformation , ExitPointType } from '../../../info'; +import { type DataflowProcessorInformation, processDataflowFor } from '../../../processor'; +import { type DataflowInformation, ExitPointType } from '../../../info'; import { collectAllIds } from '../../../../r-bridge/lang-4.x/ast/model/collect'; import type { ParentInformation } from '../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { RNode } from '../../../../r-bridge/lang-4.x/ast/model/model'; @@ -42,7 +42,7 @@ export function processFunctionArgument( tag: VertexType.Use, id: argument.info.id, cds: data.controlDependencies - }); + }, data.ctx.env.makeCleanEnv()); entryPoint = argument.info.id; } diff --git a/src/dataflow/internal/process/process-symbol.ts b/src/dataflow/internal/process/process-symbol.ts index d3ae2b201de..226310c3afe 100644 --- a/src/dataflow/internal/process/process-symbol.ts +++ b/src/dataflow/internal/process/process-symbol.ts @@ -26,7 +26,7 @@ export function processSymbol(symbol: RSymbol({ info: { id } }: RNodeWithParent, data: tag: VertexType.Value, id: id, cds: data.controlDependencies - }), + }, data.ctx.env.makeCleanEnv()), exitPoints: [{ nodeId: id, type: ExitPointType.Default, controlDependencies: data.controlDependencies }], entryPoint: id }; diff --git a/src/dataflow/processor.ts b/src/dataflow/processor.ts index ca376aa0105..cf48ff98513 100644 --- a/src/dataflow/processor.ts +++ b/src/dataflow/processor.ts @@ -7,7 +7,7 @@ import type { ParentInformation, RNodeWithParent } from '../r-bridge/lang-4.x/ast/model/processing/decorate'; -import type { IEnvironment, REnvironmentInformation } from './environments/environment'; +import type { REnvironmentInformation } from './environments/environment'; import type { RNode } from '../r-bridge/lang-4.x/ast/model/model'; import type { KnownParserType, Parser } from '../r-bridge/parser'; import type { FlowrAnalyzerContext } from '../project/context/flowr-analyzer-context'; @@ -36,10 +36,6 @@ export interface DataflowProcessorInformation { * The chain of control-flow {@link NodeId}s that lead to the current node (e.g., of known ifs). */ readonly controlDependencies: ControlDependency[] | undefined - /** - * The built-in environment - */ - readonly builtInEnvironment: IEnvironment; /** * The flowr context used for environment seeding, files, and precision control, ... */ diff --git a/src/documentation/doc-util/doc-dfg.ts b/src/documentation/doc-util/doc-dfg.ts index 5ff7fb1bf5f..1c9d4b97f35 100644 --- a/src/documentation/doc-util/doc-dfg.ts +++ b/src/documentation/doc-util/doc-dfg.ts @@ -101,14 +101,15 @@ ${switchCodeAndGraph ? dfGraph : codeText} /** returns resolved expected df graph */ export async function verifyExpectedSubgraph(parser: KnownParser, code: string, expectedSubgraph: DataflowGraph): Promise { + const context = contextFromInput(code); /* we verify that we get what we want first! */ const info = await createDataflowPipeline(parser, { - context: contextFromInput(code), + context: context, getId: deterministicCountingIdGenerator(0) }).allRemainingSteps(); expectedSubgraph.setIdMap(info.normalize.idMap); - expectedSubgraph = resolveDataflowGraph(expectedSubgraph); + expectedSubgraph = resolveDataflowGraph(expectedSubgraph, context); const report: GraphDifferenceReport = diffOfDataflowGraphs( { name: 'expected', graph: expectedSubgraph }, { name: 'got', graph: info.dataflow.graph }, diff --git a/src/documentation/doc-util/doc-normalized-ast.ts b/src/documentation/doc-util/doc-normalized-ast.ts index 0005a78da24..04ffa90ad91 100644 --- a/src/documentation/doc-util/doc-normalized-ast.ts +++ b/src/documentation/doc-util/doc-normalized-ast.ts @@ -2,9 +2,9 @@ import type { DataflowGraph } from '../../dataflow/graph/graph'; import type { RShell } from '../../r-bridge/shell'; import { createDataflowPipeline, createNormalizePipeline } from '../../core/steps/pipeline/default-pipelines'; import { + deterministicCountingIdGenerator, type ParentInformation, - type RNodeWithParent, - deterministicCountingIdGenerator + type RNodeWithParent } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; import { resolveDataflowGraph } from '../../dataflow/graph/resolve-graph'; import { diffOfDataflowGraphs } from '../../dataflow/graph/diff-dataflow-graph'; @@ -80,13 +80,14 @@ ${normalizedAstToMermaid(result.normalize.ast, prefix)} */ export async function verifyExpectedSubgraph(shell: RShell, code: string, expectedSubgraph: DataflowGraph): Promise { /* we verify that we get what we want first! */ + const context = contextFromInput(code); const info = await createDataflowPipeline(shell, { - context: contextFromInput(code), + context: context, getId: deterministicCountingIdGenerator(0) }).allRemainingSteps(); expectedSubgraph.setIdMap(info.normalize.idMap); - expectedSubgraph = resolveDataflowGraph(expectedSubgraph); + expectedSubgraph = resolveDataflowGraph(expectedSubgraph, context); const report: GraphDifferenceReport = diffOfDataflowGraphs( { name: 'expected', graph: expectedSubgraph }, { name: 'got', graph: info.dataflow.graph }, diff --git a/src/documentation/wiki-analyzer.ts b/src/documentation/wiki-analyzer.ts index cf525a2650e..7a8a5631840 100644 --- a/src/documentation/wiki-analyzer.ts +++ b/src/documentation/wiki-analyzer.ts @@ -36,6 +36,7 @@ import type { DocMakerArgs } from './wiki-mk/doc-maker'; import { DocMaker } from './wiki-mk/doc-maker'; import { FlowrAnalyzerRmdFilePlugin } from '../project/plugins/file-plugins/notebooks/flowr-analyzer-rmd-file-plugin'; import { FlowrAnalyzerPlugin } from '../project/plugins/flowr-analyzer-plugin'; +import { FlowrAnalyzerEnvironmentContext } from '../project/context/flowr-analyzer-environment-context'; async function analyzerQuickExample() { const analyzer = await new FlowrAnalyzerBuilder() @@ -97,7 +98,8 @@ ${ 'Context Information': { 'Files Context': undefined, 'Loading Order Context': undefined, - 'Dependencies Context': undefined + 'Dependencies Context': undefined, + 'Environment Context': undefined }, 'Caching': undefined }) @@ -434,6 +436,17 @@ Probably the most important method is ${ctx.linkM(FlowrAnalyzerDependenciesContext, 'getDependency', { codeFont: true, realNameWrapper: 'i' })} that allows you to query for a specific dependency by name. +${section('Environment Context', 3)} + +Here is the structure of the ${ctx.link(FlowrAnalyzerEnvironmentContext)} that provides access to the built-in environment: + +${ctx.hierarchy(FlowrAnalyzerEnvironmentContext, { showImplSnippet: false })} + +The environment context provides access to the built-in environment via +${ctx.linkM(FlowrAnalyzerEnvironmentContext, 'makeCleanEnv', { codeFont: true, realNameWrapper: 'i' })}. +It also provides the empty built-in environment, which only contains primitives, via +${ctx.linkM(FlowrAnalyzerEnvironmentContext, 'makeCleanEnvWithEmptyBuiltIns', { codeFont: true, realNameWrapper: 'i' })}. + ${section('Caching', 2)} To speed up analyses, flowR provides a caching mechanism that stores intermediate results of the analysis. diff --git a/src/linter/rules/absolute-path.ts b/src/linter/rules/absolute-path.ts index 02665d263b9..1c8e57f4994 100644 --- a/src/linter/rules/absolute-path.ts +++ b/src/linter/rules/absolute-path.ts @@ -1,7 +1,14 @@ -import { type LintingResult, type LintingRule, type LintQuickFixReplacement , LintingResultCertainty, LintingPrettyPrintContext, LintingRuleCertainty } from '../linter-format'; -import { type MergeableRecord , compactRecord } from '../../util/objects'; +import { + LintingPrettyPrintContext, + type LintingResult, + LintingResultCertainty, + type LintingRule, + LintingRuleCertainty, + type LintQuickFixReplacement +} from '../linter-format'; +import { compactRecord, type MergeableRecord } from '../../util/objects'; import { Q } from '../../search/flowr-search-builder'; -import { type SourceRange , rangeFrom } from '../../util/range'; +import { rangeFrom, type SourceRange } from '../../util/range'; import { formatRange } from '../../util/mermaid/dfg'; import { LintingRuleTag } from '../linter-tags'; import { RType } from '../../r-bridge/lang-4.x/ast/model/type'; @@ -13,14 +20,14 @@ import { WriteFunctions } from '../../queries/catalog/dependencies-query/functio import type { FunctionInfo } from '../../queries/catalog/dependencies-query/function-info/function-info'; import { Enrichment, enrichmentContent } from '../../search/search-executor/search-enrichers'; import { SourceFunctions } from '../../queries/catalog/dependencies-query/function-info/source-functions'; -import { type DataflowGraphVertexFunctionCall , isFunctionCallVertex, VertexType } from '../../dataflow/graph/vertex'; +import { type DataflowGraphVertexFunctionCall, isFunctionCallVertex, VertexType } from '../../dataflow/graph/vertex'; import type { QueryResults } from '../../queries/query'; import { Unknown } from '../../queries/catalog/dependencies-query/dependencies-query-format'; import type { DataflowGraph } from '../../dataflow/graph/graph'; import { getArgumentStringValue } from '../../dataflow/eval/resolve/resolve-argument'; import path from 'path'; import type { RNode } from '../../r-bridge/lang-4.x/ast/model/model'; -import type { FlowrConfigOptions } from '../../config'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context'; export interface AbsoluteFilePathResult extends LintingResult { filePath: string, @@ -81,11 +88,13 @@ function buildQuickFix(str: RNode | undefined, filePath: string, wd: string | un }]; } +type PathFunction = (df: DataflowGraph, vtx: DataflowGraphVertexFunctionCall, ctx: ReadOnlyFlowrAnalyzerContext) => string[] | undefined; + /** return all strings constructable by these functions */ -const PathFunctions: Record string[] | undefined> = { - 'file.path': (df: DataflowGraph, vtx: DataflowGraphVertexFunctionCall, config: FlowrConfigOptions): string[] | undefined => { - const fsep = getArgumentStringValue(config.solver.variables, - df, vtx, undefined, 'fsep', true +const PathFunctions: Record = { + 'file.path': (df: DataflowGraph, vtx: DataflowGraphVertexFunctionCall, ctx: ReadOnlyFlowrAnalyzerContext): string[] | undefined => { + const fsep = getArgumentStringValue(ctx.config.solver.variables, + df, vtx, undefined, 'fsep', true, ctx ); // in the future we can access `.Platform$file.sep` here const sepValues: string[] = fsep?.values()?.flatMap(s => s.values().filter(isNotUndefined)).toArray() ?? [path.sep]; @@ -93,7 +102,7 @@ const PathFunctions: Record [...v]) : []; if(!argValues || argValues.length === 0 || argValues.some(v => v === Unknown || isUndefined(v))) { // if we have no arguments, we cannot construct a path @@ -171,7 +180,7 @@ export const ABSOLUTE_PATH = { const dfNode = data.dataflow.graph.getVertex(node.info.id); if(isFunctionCallVertex(dfNode)) { const handler = PathFunctions[dfNode.name ?? '']; - const strings = handler ? handler(data.dataflow.graph, dfNode, data.analyzer.flowrConfig) : []; + const strings = handler ? handler(data.dataflow.graph, dfNode, data.analyzer.inspectContext()) : []; if(strings) { return strings.filter(s => isAbsolutePath(s, regex)).map(str => ({ certainty: LintingResultCertainty.Uncertain, diff --git a/src/linter/rules/function-finder-util.ts b/src/linter/rules/function-finder-util.ts index b47b50d23ce..07817d13c65 100644 --- a/src/linter/rules/function-finder-util.ts +++ b/src/linter/rules/function-finder-util.ts @@ -1,11 +1,9 @@ - - import { Q } from '../../search/flowr-search-builder'; import { FlowrFilter, testFunctionsIgnoringPackage } from '../../search/flowr-search-filters'; import { Enrichment, enrichmentContent } from '../../search/search-executor/search-enrichers'; import type { SourceRange } from '../../util/range'; import type { Identifier } from '../../dataflow/environments/identifier'; -import { type LintingResult , LintingResultCertainty, LintingPrettyPrintContext } from '../linter-format'; +import { LintingPrettyPrintContext, type LintingResult, LintingResultCertainty } from '../linter-format'; import type { FlowrSearchElement, FlowrSearchElements } from '../../search/flowr-search'; import type { NormalizedAst, ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; import type { MergeableRecord } from '../../util/objects'; @@ -13,10 +11,10 @@ import { formatRange } from '../../util/mermaid/dfg'; import { isNotUndefined } from '../../util/assert'; import { getArgumentStringValue } from '../../dataflow/eval/resolve/resolve-argument'; import type { DataflowInformation } from '../../dataflow/info'; -import type { FlowrConfigOptions } from '../../config'; import { isFunctionCallVertex } from '../../dataflow/graph/vertex'; import type { FunctionInfo } from '../../queries/catalog/dependencies-query/function-info/function-info'; import { Unknown } from '../../queries/catalog/dependencies-query/dependencies-query-format'; +import type { ReadonlyFlowrAnalysisProvider } from '../../project/flowr-analyzer'; export interface FunctionsResult extends LintingResult { function: string @@ -96,7 +94,7 @@ export const functionFinderUtil = { requireArgumentValue( element: FlowrSearchElement, pool: readonly FunctionInfo[], - data: { normalize: NormalizedAst, dataflow: DataflowInformation, config: FlowrConfigOptions}, + data: { normalize: NormalizedAst, dataflow: DataflowInformation, analyzer: ReadonlyFlowrAnalysisProvider}, requireValue: RegExp | string | undefined ): boolean { const info = pool.find(f => f.name === element.node.lexeme); @@ -107,12 +105,13 @@ export const functionFinderUtil = { const vert = data.dataflow.graph.getVertex(element.node.info.id); if(isFunctionCallVertex(vert)){ const args = getArgumentStringValue( - data.config.solver.variables, + data.analyzer.flowrConfig.solver.variables, data.dataflow.graph, vert, info.argIdx, info.argName, - info.resolveValue); + info.resolveValue, + data.analyzer.inspectContext()); // we obtain all values, at least one of them has to trigger for the request const argValues: string[] = args ? args.values().flatMap(v => [...v]).filter(isNotUndefined).toArray() : []; diff --git a/src/linter/rules/network-functions.ts b/src/linter/rules/network-functions.ts index 8313e4f94a0..51613806616 100644 --- a/src/linter/rules/network-functions.ts +++ b/src/linter/rules/network-functions.ts @@ -1,5 +1,5 @@ -import { LintingRuleCertainty, type LintingRule } from '../linter-format'; -import { type FunctionsMetadata, type FunctionsResult , functionFinderUtil } from './function-finder-util'; +import { type LintingRule, LintingRuleCertainty } from '../linter-format'; +import { functionFinderUtil, type FunctionsMetadata, type FunctionsResult } from './function-finder-util'; import { LintingRuleTag } from '../linter-tags'; import { ReadFunctions } from '../../queries/catalog/dependencies-query/function-info/read-functions'; import type { MergeableRecord } from '../../util/objects'; @@ -18,7 +18,7 @@ export const NETWORK_FUNCTIONS = { es.filter(e => functionFinderUtil.requireArgumentValue( e, ReadFunctions, - { config: d.analyzer.flowrConfig, dataflow: d.dataflow, normalize: d.normalize }, + { analyzer: d.analyzer, dataflow: d.dataflow, normalize: d.normalize }, c.onlyTriggerWithArgument )) ), diff --git a/src/linter/rules/seeded-randomness.ts b/src/linter/rules/seeded-randomness.ts index 98baf8e742d..7b6ba0a5d5b 100644 --- a/src/linter/rules/seeded-randomness.ts +++ b/src/linter/rules/seeded-randomness.ts @@ -1,4 +1,10 @@ -import { type LintingResult, type LintingRule , LintingResultCertainty, LintingPrettyPrintContext, LintingRuleCertainty } from '../linter-format'; +import { + LintingPrettyPrintContext, + type LintingResult, + LintingResultCertainty, + type LintingRule, + LintingRuleCertainty +} from '../linter-format'; import type { SourceRange } from '../../util/range'; import type { MergeableRecord } from '../../util/objects'; import { Q } from '../../search/flowr-search-builder'; @@ -7,7 +13,7 @@ import { Enrichment, enrichmentContent } from '../../search/search-executor/sear import type { Identifier } from '../../dataflow/environments/identifier'; import { FlowrFilter, testFunctionsIgnoringPackage } from '../../search/flowr-search-filters'; import { DefaultBuiltinConfig } from '../../dataflow/environments/default-builtin-config'; -import { type DataflowGraph , getReferenceOfArgument } from '../../dataflow/graph/graph'; +import { type DataflowGraph, getReferenceOfArgument } from '../../dataflow/graph/graph'; import { CascadeAction } from '../../queries/catalog/call-context-query/cascade-action'; import { recoverName } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { LintingRuleTag } from '../linter-tags'; @@ -18,6 +24,7 @@ import { VariableResolve } from '../../config'; import type { DataflowGraphVertexFunctionCall } from '../../dataflow/graph/vertex'; import { EmptyArgument } from '../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; import { asValue } from '../../dataflow/eval/values/r-value'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context'; import { happensInEveryBranchSet } from '../../dataflow/info'; export interface SeededRandomnessResult extends LintingResult { @@ -60,7 +67,7 @@ export const SEEDED_RANDOMNESS = { { callName: config.randomnessProducers.filter(p => p.type === 'function').map(p => p.name) }, { callName: getDefaultAssignments().flatMap(b => b.names), cascadeIf: () => CascadeAction.Continue } ]), - processSearchResult: (elements, config, { dataflow }) => { + processSearchResult: (elements, config, { dataflow, analyzer }) => { const assignmentProducers = new Set(config.randomnessProducers.filter(p => p.type == 'assignment').map(p => p.name)); const assignmentArgIndexes = new Map(getDefaultAssignments().flatMap(a => a.names.map(n => ([n, a.config?.swapSourceAndTarget ? 1 : 0])))); const metadata: SeededRandomnessMeta = { @@ -93,7 +100,7 @@ export const SEEDED_RANDOMNESS = { // function calls are already taken care of through the LastCall enrichment itself for(const f of func ?? []) { - if(isConstantArgument(dataflow.graph, f, 0)) { + if(isConstantArgument(dataflow.graph, f, 0, analyzer.inspectContext())) { const fCds = new Set(f.cds).difference(cds); if(fCds.size <= 0 || happensInEveryBranchSet(fCds)){ metadata.callsWithFunctionProducers++; @@ -112,7 +119,7 @@ export const SEEDED_RANDOMNESS = { const dest = getReferenceOfArgument(a.args[argIdx]); if(dest !== undefined && assignmentProducers.has(recoverName(dest, dataflow.graph.idMap) as string)){ // we either have arg index 0 or 1 for the assignmentProducers destination, so we select the assignment value as 1-argIdx here - if(isConstantArgument(dataflow.graph, a, 1-argIdx)) { + if(isConstantArgument(dataflow.graph, a, 1-argIdx, analyzer.inspectContext())) { const aCds = new Set(a.cds).difference(cds); if(aCds.size <= 0 || happensInEveryBranchSet(aCds)) { metadata.callsWithAssignmentProducers++; @@ -162,9 +169,9 @@ function getDefaultAssignments(): BuiltInFunctionDefinition<'builtin:assignment' return DefaultBuiltinConfig.filter(b => b.type === 'function' && b.processor == 'builtin:assignment') as BuiltInFunctionDefinition<'builtin:assignment'>[]; } -function isConstantArgument(graph: DataflowGraph, call: DataflowGraphVertexFunctionCall, argIndex: number): boolean { +function isConstantArgument(graph: DataflowGraph, call: DataflowGraphVertexFunctionCall, argIndex: number, ctx: ReadOnlyFlowrAnalyzerContext): boolean { const args = call.args.filter(arg => arg !== EmptyArgument && !arg.name).map(getReferenceOfArgument); - const values = valueSetGuard(resolveIdToValue(args[argIndex], { graph: graph, resolve: VariableResolve.Alias })); + const values = valueSetGuard(resolveIdToValue(args[argIndex], { graph: graph, resolve: VariableResolve.Alias, ctx })); return values?.elements.every(v => v.type === 'number' || v.type === 'logical' || diff --git a/src/project/context/flowr-analyzer-context.ts b/src/project/context/flowr-analyzer-context.ts index 9506b7a2024..ebff6ad47a4 100644 --- a/src/project/context/flowr-analyzer-context.ts +++ b/src/project/context/flowr-analyzer-context.ts @@ -26,6 +26,8 @@ import type { FlowrConfigOptions } from '../../config'; import { defaultConfigOptions } from '../../config'; import type { FlowrFileProvider } from './flowr-file'; import { FlowrInlineTextFile } from './flowr-file'; +import type { ReadOnlyFlowrAnalyzerEnvironmentContext } from './flowr-analyzer-environment-context'; +import { FlowrAnalyzerEnvironmentContext } from './flowr-analyzer-environment-context'; /** * This is a read-only interface to the {@link FlowrAnalyzerContext}. @@ -41,6 +43,10 @@ export interface ReadOnlyFlowrAnalyzerContext { * The dependencies context provides access to the identified dependencies and their versions. */ readonly deps: ReadOnlyFlowrAnalyzerDependenciesContext; + /** + * The environment context provides access to the environment information used during analysis. + */ + readonly env: ReadOnlyFlowrAnalyzerEnvironmentContext; /** * The configuration options used by the analyzer. */ @@ -60,8 +66,10 @@ export interface ReadOnlyFlowrAnalyzerContext { * If you are just interested in inspecting the context, you can use {@link ReadOnlyFlowrAnalyzerContext} instead (e.g., via {@link inspect}). */ export class FlowrAnalyzerContext implements ReadOnlyFlowrAnalyzerContext { - public readonly files: FlowrAnalyzerFilesContext; - public readonly deps: FlowrAnalyzerDependenciesContext; + public readonly files: FlowrAnalyzerFilesContext; + public readonly deps: FlowrAnalyzerDependenciesContext; + public readonly env: FlowrAnalyzerEnvironmentContext; + public readonly config: FlowrConfigOptions; constructor(config: FlowrConfigOptions, plugins: ReadonlyMap) { @@ -70,6 +78,7 @@ export class FlowrAnalyzerContext implements ReadOnlyFlowrAnalyzerContext { this.files = new FlowrAnalyzerFilesContext(loadingOrder, (plugins.get(PluginType.ProjectDiscovery) ?? []) as FlowrAnalyzerProjectDiscoveryPlugin[], (plugins.get(PluginType.FileLoad) ?? []) as FlowrAnalyzerFilePlugin[]); this.deps = new FlowrAnalyzerDependenciesContext(this, (plugins.get(PluginType.DependencyIdentification) ?? []) as FlowrAnalyzerPackageVersionsPlugin[]); + this.env = new FlowrAnalyzerEnvironmentContext(this); } /** delegate request addition */ diff --git a/src/project/context/flowr-analyzer-environment-context.ts b/src/project/context/flowr-analyzer-environment-context.ts new file mode 100644 index 00000000000..21d0bb3a66f --- /dev/null +++ b/src/project/context/flowr-analyzer-environment-context.ts @@ -0,0 +1,92 @@ +import type { FlowrAnalyzerContext } from './flowr-analyzer-context'; +import type { IEnvironment, REnvironmentInformation } from '../../dataflow/environments/environment'; +import { Environment } from '../../dataflow/environments/environment'; +import type { DeepReadonly } from 'ts-essentials'; +import { getBuiltInDefinitions } from '../../dataflow/environments/built-in-config'; +import type { Fingerprint } from '../../slicing/static/fingerprint'; +import { envFingerprint } from '../../slicing/static/fingerprint'; + +/** + * This is the read-only interface to the {@link FlowrAnalyzerEnvironmentContext}, + * which provides access to the built-in environment used during analysis. + */ +export interface ReadOnlyFlowrAnalyzerEnvironmentContext { + /** + * Get the built-in environment used during analysis. + */ + get builtInEnvironment(): DeepReadonly; + + /** + * Get the empty built-in environment used during analysis. + * The empty built-in environment only contains primitive definitions. + */ + get emptyBuiltInEnvironment(): DeepReadonly; + + /** + * Create a new {@link REnvironmentInformation|environment} with the configured built-in environment as base. + */ + makeCleanEnv(): REnvironmentInformation; + + /** + * Get the fingerprint of the clean environment with the configured built-in environment as base. + */ + getCleanEnvFingerprint(): Fingerprint; + + /** + * Create a new {@link REnvironmentInformation|environment} with an empty built-in environment as base. + */ + makeCleanEnvWithEmptyBuiltIns(): REnvironmentInformation; +} + +/** + * This context is responsible for providing the built-in environment. + * It creates the built-in environment based on the configuration provided in the {@link FlowrAnalyzerContext}. + */ +export class FlowrAnalyzerEnvironmentContext implements ReadOnlyFlowrAnalyzerEnvironmentContext { + public readonly name = 'flowr-analyzer-environment-context'; + private readonly builtInEnv: IEnvironment; + private readonly emptyBuiltInEnv: IEnvironment; + + private builtInEnvFingerprint: Fingerprint | undefined; + + constructor(ctx: FlowrAnalyzerContext) { + const builtInsConfig = ctx.config.semantics.environment.overwriteBuiltIns; + const builtIns = getBuiltInDefinitions(builtInsConfig.definitions, builtInsConfig.loadDefaults); + + this.builtInEnv = new Environment(undefined as unknown as IEnvironment, true); + this.builtInEnv.memory = builtIns.builtInMemory; + + this.emptyBuiltInEnv = new Environment(undefined as unknown as IEnvironment, true); + this.emptyBuiltInEnv.memory = builtIns.emptyBuiltInMemory; + } + + public get builtInEnvironment(): DeepReadonly { + return this.builtInEnv; + } + + public get emptyBuiltInEnvironment(): DeepReadonly { + return this.emptyBuiltInEnv; + } + + public makeCleanEnv(): REnvironmentInformation { + return { + current: new Environment(this.builtInEnv), + level: 0 + }; + } + + public getCleanEnvFingerprint(): Fingerprint { + if(!this.builtInEnvFingerprint) { + this.builtInEnvFingerprint = envFingerprint(this.makeCleanEnv()); + } + return this.builtInEnvFingerprint; + } + + public makeCleanEnvWithEmptyBuiltIns(): REnvironmentInformation { + return { + current: new Environment(this.emptyBuiltInEnv), + level: 0 + }; + } +} + diff --git a/src/queries/catalog/dataflow-lens-query/dataflow-lens-query-executor.ts b/src/queries/catalog/dataflow-lens-query/dataflow-lens-query-executor.ts index 6603e1e1750..8bde5a79bc3 100644 --- a/src/queries/catalog/dataflow-lens-query/dataflow-lens-query-executor.ts +++ b/src/queries/catalog/dataflow-lens-query/dataflow-lens-query-executor.ts @@ -21,7 +21,7 @@ export async function executeDataflowLensQuery({ analyzer }: BasicQueryData, que nameRegex: '<-|<<-|->|->>|=|+|-|*|/|\\|>|function|repeat|if|next|break', blacklistWithName: true } - }); + }, analyzer.inspectContext().env.makeCleanEnv()); const timing = Date.now() - now; return { diff --git a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts index 7a613c83ead..4c673b45ce0 100644 --- a/src/queries/catalog/dependencies-query/dependencies-query-executor.ts +++ b/src/queries/catalog/dependencies-query/dependencies-query-executor.ts @@ -1,25 +1,33 @@ import { executeQueriesOfSameType } from '../../query'; import { + DefaultDependencyCategories, type DefaultDependencyCategoryName, type DependenciesQuery, type DependenciesQueryResult, type DependencyCategoryName, type DependencyCategorySettings, - type DependencyInfo - , DefaultDependencyCategories, getAllCategories, Unknown } from './dependencies-query-format'; + type DependencyInfo, + getAllCategories, + Unknown +} from './dependencies-query-format'; import type { CallContextQuery, CallContextQueryResult } from '../call-context-query/call-context-query-format'; -import { type DataflowGraphVertexFunctionCall , VertexType } from '../../../dataflow/graph/vertex'; +import { type DataflowGraphVertexFunctionCall, VertexType } from '../../../dataflow/graph/vertex'; import { log } from '../../../util/log'; import { RType } from '../../../r-bridge/lang-4.x/ast/model/type'; import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import type { BasicQueryData } from '../../base-query-format'; import { compactRecord } from '../../../util/objects'; -import { type DependencyInfoLinkAttachedInfo, type FunctionInfo , DependencyInfoLinkConstraint } from './function-info/function-info'; +import { + type DependencyInfoLinkAttachedInfo, + DependencyInfoLinkConstraint, + type FunctionInfo +} from './function-info/function-info'; import { CallTargets } from '../call-context-query/identify-link-to-last-call-relation'; import { getArgumentStringValue } from '../../../dataflow/eval/resolve/resolve-argument'; import type { DataflowInformation } from '../../../dataflow/info'; import type { FlowrConfigOptions } from '../../../config'; import { guard } from '../../../util/assert'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context'; import type { NormalizedAst } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate'; @@ -93,7 +101,7 @@ function dropInfoOnLinkedIds(linkedIds: readonly (NodeId | { id: NodeId, info: o const readOnlyModes = new Set(['r', 'rt', 'rb']); const writeOnlyModes = new Set(['w', 'wt', 'wb', 'a', 'at', 'ab']); -function getResults(queries: readonly DependenciesQuery[], { dataflow, config, normalize }: { dataflow: DataflowInformation, config: FlowrConfigOptions, normalize: NormalizedAst }, results: CallContextQueryResult, kind: DependencyCategoryName, functions: FunctionInfo[], data?: BasicQueryData): DependencyInfo[] { +function getResults(queries: readonly DependenciesQuery[], { dataflow, config, normalize }: { dataflow: DataflowInformation, config: FlowrConfigOptions, normalize: NormalizedAst }, results: CallContextQueryResult, kind: DependencyCategoryName, functions: FunctionInfo[], data: BasicQueryData): DependencyInfo[] { const defaultValue = getAllCategories(queries)[kind].defaultValue; const functionMap = new Map(functions.map(f => [f.name, f])); const kindEntries = Object.entries(results?.kinds[kind]?.subkinds ?? {}); @@ -101,8 +109,8 @@ function getResults(queries: readonly DependenciesQuery[], { dataflow, config, n const vertex = dataflow.graph.getVertex(id) as DataflowGraphVertexFunctionCall; const info = functionMap.get(name) as FunctionInfo; - const args = getArgumentStringValue(config.solver.variables, dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue); - const linkedArgs = collectValuesFromLinks(args, { dataflow, config }, linkedIds as (NodeId | { id: NodeId, info: DependencyInfoLinkAttachedInfo })[] | undefined); + const args = getArgumentStringValue(config.solver.variables, dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue, data.analyzer.inspectContext()); + const linkedArgs = collectValuesFromLinks(args, { dataflow, config, ctx: data.analyzer.inspectContext() }, linkedIds as (NodeId | { id: NodeId, info: DependencyInfoLinkAttachedInfo })[] | undefined); const linked = dropInfoOnLinkedIds(linkedIds); const foundValues = linkedArgs ?? args; @@ -122,7 +130,7 @@ function getResults(queries: readonly DependenciesQuery[], { dataflow, config, n guard('mode' in (info.additionalArgs ?? {}), 'Need additional argument mode when checking for mode'); const margs = info.additionalArgs?.mode; guard(margs, 'Need additional argument mode when checking for mode'); - const modeArgs = getArgumentStringValue(config.solver.variables, dataflow.graph, vertex, margs.argIdx, margs.argName, margs.resolveValue); + const modeArgs = getArgumentStringValue(config.solver.variables, dataflow.graph, vertex, margs.argIdx, margs.argName, margs.resolveValue, data?.analyzer.inspectContext()); const modeValues = modeArgs?.values().flatMap(v => [...v]) ?? []; if(info.ignoreIf === 'mode-only-read' && modeValues.every(m => m && readOnlyModes.has(m))) { // all modes are read-only, so we can ignore this @@ -165,7 +173,7 @@ function getResults(queries: readonly DependenciesQuery[], { dataflow, config, n } } -function collectValuesFromLinks(args: Map> | undefined, data: { dataflow: DataflowInformation, config: FlowrConfigOptions }, linkedIds: readonly (NodeId | { id: NodeId, info: DependencyInfoLinkAttachedInfo })[] | undefined): Map> | undefined { +function collectValuesFromLinks(args: Map> | undefined, data: { dataflow: DataflowInformation, config: FlowrConfigOptions, ctx: ReadOnlyFlowrAnalyzerContext }, linkedIds: readonly (NodeId | { id: NodeId, info: DependencyInfoLinkAttachedInfo })[] | undefined): Map> | undefined { if(!linkedIds || linkedIds.length === 0) { return undefined; } @@ -185,7 +193,7 @@ function collectValuesFromLinks(args: Map> | undef if(vertex === undefined || vertex.tag !== VertexType.FunctionCall) { continue; } - const args = getArgumentStringValue(data.config.solver.variables, data.dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue); + const args = getArgumentStringValue(data.config.solver.variables, data.dataflow.graph, vertex, info.argIdx, info.argName, info.resolveValue, data.ctx); if(args === undefined) { continue; } diff --git a/src/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-executor.ts b/src/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-executor.ts index 2f00d0af209..66a40fe4f78 100644 --- a/src/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-executor.ts +++ b/src/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-executor.ts @@ -1,8 +1,6 @@ -import type { - InspectHigherOrderQuery, InspectHigherOrderQueryResult -} from './inspect-higher-order-query-format'; +import type { InspectHigherOrderQuery, InspectHigherOrderQueryResult } from './inspect-higher-order-query-format'; import type { BasicQueryData } from '../../base-query-format'; -import { type SingleSlicingCriterion , tryResolveSliceCriterionToId } from '../../../slicing/criterion/parse'; +import { type SingleSlicingCriterion, tryResolveSliceCriterionToId } from '../../../slicing/criterion/parse'; import { isFunctionDefinitionVertex } from '../../../dataflow/graph/vertex'; import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { isHigherOrder } from '../../../dataflow/fn/higher-order-function'; @@ -42,7 +40,7 @@ export async function executeHigherOrderQuery({ analyzer }: BasicQueryData, quer .filter(([,v]) => isFunctionDefinitionVertex(v) && (filterFor.size === 0 || filterFor.has(v.id))); const result: Record = {}; for(const [id,] of fns) { - result[id] = isHigherOrder(id, graph); + result[id] = isHigherOrder(id, graph, analyzer.inspectContext()); } return { 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 0b3b90e643d..8be5e3aeea6 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 @@ -32,7 +32,7 @@ export async function executeResolveValueQuery({ analyzer }: BasicQueryData, que const values = query.criteria .map(criteria => slicingCriterionToId(criteria, ast.idMap)) - .flatMap(ident => resolveIdToValue(ident, { graph, full: true, idMap: ast.idMap, resolve: analyzer.flowrConfig.solver.variables })); + .flatMap(ident => resolveIdToValue(ident, { graph, full: true, idMap: ast.idMap, resolve: analyzer.flowrConfig.solver.variables, ctx: analyzer.inspectContext() })); results[key] = { values: values diff --git a/src/queries/catalog/static-slice-query/static-slice-query-executor.ts b/src/queries/catalog/static-slice-query/static-slice-query-executor.ts index 7f1bdc402eb..898dcd7a405 100644 --- a/src/queries/catalog/static-slice-query/static-slice-query-executor.ts +++ b/src/queries/catalog/static-slice-query/static-slice-query-executor.ts @@ -30,7 +30,7 @@ export async function executeStaticSliceQuery({ analyzer }: BasicQueryData, quer } const { criteria, noReconstruction, noMagicComments } = query; const sliceStart = Date.now(); - const slice = staticSlice(await analyzer.dataflow(), await analyzer.normalize(), criteria, query.direction ?? SliceDirection.Backward, analyzer.flowrConfig.solver.slicer?.threshold); + const slice = staticSlice(analyzer.inspectContext(), await analyzer.dataflow(), await analyzer.normalize(), criteria, query.direction ?? SliceDirection.Backward, analyzer.flowrConfig.solver.slicer?.threshold); const sliceEnd = Date.now(); if(noReconstruction) { results[key] = { slice: { ...slice, '.meta': { timing: sliceEnd - sliceStart } } }; diff --git a/src/slicing/static/fingerprint.ts b/src/slicing/static/fingerprint.ts index b190b13f98a..80898873ae5 100644 --- a/src/slicing/static/fingerprint.ts +++ b/src/slicing/static/fingerprint.ts @@ -1,5 +1,5 @@ import objectHash from 'object-hash'; -import { type REnvironmentInformation , isDefaultBuiltInEnvironment } from '../../dataflow/environments/environment'; +import { isDefaultBuiltInEnvironment, type REnvironmentInformation } from '../../dataflow/environments/environment'; import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; export type Fingerprint = string diff --git a/src/slicing/static/slice-call.ts b/src/slicing/static/slice-call.ts index f32e188a8ec..f2ab700501a 100644 --- a/src/slicing/static/slice-call.ts +++ b/src/slicing/static/slice-call.ts @@ -1,7 +1,7 @@ import type { NodeToSlice } from './slicer-types'; import type { VisitingQueue } from './visiting-queue'; import { guard } from '../../util/assert'; -import { type Fingerprint , envFingerprint } from './fingerprint'; +import { envFingerprint, type Fingerprint } from './fingerprint'; import { getAllLinkedFunctionDefinitions } from '../../dataflow/internal/linker'; import type { DataflowGraphVertexFunctionCall, @@ -9,7 +9,12 @@ import type { DataflowGraphVertexInfo } from '../../dataflow/graph/vertex'; import type { REnvironmentInformation } from '../../dataflow/environments/environment'; -import { type DataflowGraph, type FunctionArgument, type OutgoingEdges , getReferenceOfArgument } from '../../dataflow/graph/graph'; +import { + type DataflowGraph, + type FunctionArgument, + getReferenceOfArgument, + type OutgoingEdges +} from '../../dataflow/graph/graph'; import { isBuiltIn } from '../../dataflow/environments/built-in'; import { resolveByName } from '../../dataflow/environments/resolve-by-name'; import { edgeIncludesType, EdgeType } from '../../dataflow/graph/edge'; @@ -20,18 +25,19 @@ import { } from '../../dataflow/internal/process/functions/call/built-in/built-in-function-definition'; import { updatePotentialAddition } from './static-slicer'; import type { DataflowInformation } from '../../dataflow/info'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context'; /** * Returns the function call targets (definitions) by the given caller */ -export function getAllFunctionCallTargets(dataflowGraph: DataflowGraph, callerInfo: DataflowGraphVertexFunctionCall, baseEnvironment: REnvironmentInformation, queue: VisitingQueue): [Set, REnvironmentInformation] { +export function getAllFunctionCallTargets(dataflowGraph: DataflowGraph, callerInfo: DataflowGraphVertexFunctionCall, baseEnvironment: REnvironmentInformation, queue: VisitingQueue, ctx: ReadOnlyFlowrAnalyzerContext): [Set, REnvironmentInformation] { // bind with call-local environments during slicing const outgoingEdges = dataflowGraph.get(callerInfo.id, true); guard(outgoingEdges !== undefined, () => `outgoing edges of id: ${callerInfo.id} must be in graph but can not be found, keep in slice to be sure`); // lift baseEnv on the same level - const activeEnvironment = retrieveActiveEnvironment(callerInfo.environment, baseEnvironment); + const activeEnvironment = retrieveActiveEnvironment(callerInfo.environment, baseEnvironment, ctx); const name = callerInfo.name; guard(name !== undefined, () => `name of id: ${callerInfo.id} can not be found in id map`); @@ -87,9 +93,9 @@ function linkCallTargets( } /** returns the new threshold hit count */ -export function sliceForCall(current: NodeToSlice, callerInfo: DataflowGraphVertexFunctionCall, dataflowInformation: DataflowInformation, queue: VisitingQueue): void { +export function sliceForCall(current: NodeToSlice, callerInfo: DataflowGraphVertexFunctionCall, dataflowInformation: DataflowInformation, queue: VisitingQueue, ctx: ReadOnlyFlowrAnalyzerContext): void { const baseEnvironment = current.baseEnvironment; - const [functionCallTargets, activeEnvironment] = getAllFunctionCallTargets(dataflowInformation.graph, callerInfo, current.baseEnvironment, queue); + const [functionCallTargets, activeEnvironment] = getAllFunctionCallTargets(dataflowInformation.graph, callerInfo, current.baseEnvironment, queue, ctx); const activeEnvironmentFingerprint = envFingerprint(activeEnvironment); if(functionCallTargets.size === 0) { diff --git a/src/slicing/static/static-slicer.ts b/src/slicing/static/static-slicer.ts index 7e49de8367a..8310832cc94 100644 --- a/src/slicing/static/static-slicer.ts +++ b/src/slicing/static/static-slicer.ts @@ -1,18 +1,19 @@ import { assertUnreachable, guard } from '../../util/assert'; import { expensiveTrace, log } from '../../util/log'; import type { SliceResult } from './slicer-types'; -import { type Fingerprint , envFingerprint } from './fingerprint'; +import { type Fingerprint } from './fingerprint'; import { VisitingQueue } from './visiting-queue'; import { handleReturns, sliceForCall } from './slice-call'; import type { NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; -import { type SlicingCriteria , convertAllSlicingCriteriaToIds } from '../criterion/parse'; -import { type REnvironmentInformation , initializeCleanEnvironments } from '../../dataflow/environments/environment'; +import { convertAllSlicingCriteriaToIds, type SlicingCriteria } from '../criterion/parse'; +import { type REnvironmentInformation } from '../../dataflow/environments/environment'; import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id'; import { VertexType } from '../../dataflow/graph/vertex'; import { shouldTraverseEdge, TraverseEdge } from '../../dataflow/graph/edge'; import { SliceDirection } from '../../core/steps/all/static-slicing/00-slice'; import { invertDfg } from '../../dataflow/graph/invert-dfg'; import type { DataflowInformation } from '../../dataflow/info'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-analyzer-context'; export const slicerLogger = log.getSubLogger({ name: 'slicer' }); @@ -20,6 +21,7 @@ export const slicerLogger = log.getSubLogger({ name: 'slicer' }); * This returns the ids to include in the static slice of the given type, when slicing with the given seed id's (must be at least one). *

* The returned ids can be used to {@link reconstructToCode|reconstruct the slice to R code}. + * @param ctx - The analyzer context used for slicing. * @param info - The dataflow information used for slicing. * @param idMap - The mapping from node ids to their information in the AST. * @param criteria - The criteria to slice on. @@ -28,6 +30,7 @@ export const slicerLogger = log.getSubLogger({ name: 'slicer' }); * @param cache - A cache to store the results of the slice. If provided, the slice may use this cache to speed up the slicing process. */ export function staticSlice( + ctx: ReadOnlyFlowrAnalyzerContext, info: DataflowInformation, { idMap }: NormalizedAst, criteria: SlicingCriteria, @@ -44,7 +47,7 @@ export function staticSlice( let { graph } = info; if(direction === SliceDirection.Forward){ - graph = invertDfg(graph); + graph = invertDfg(graph, ctx.env.makeCleanEnv()); } const queue = new VisitingQueue(threshold, cache); @@ -53,8 +56,8 @@ export function staticSlice( const sliceSeedIds = new Set(); // every node ships the call environment which registers the calling environment { - const emptyEnv = initializeCleanEnvironments(); - const basePrint = envFingerprint(emptyEnv); + const emptyEnv = ctx.env.makeCleanEnv(); + const basePrint = ctx.env.getCleanEnvFingerprint(); for(const { id: startId } of decodedCriteria) { queue.add(startId, emptyEnv, basePrint, false); // retrieve the minimum nesting of all nodes to only add control dependencies if they are "part" of the current execution @@ -98,7 +101,7 @@ export function staticSlice( if(!onlyForSideEffects) { if(currentVertex.tag === VertexType.FunctionCall && !currentVertex.onlyBuiltin) { - sliceForCall(current, currentVertex, info, queue); + sliceForCall(current, currentVertex, info, queue, ctx); } const ret = handleReturns(id, queue, currentEdges, baseEnvFingerprint, baseEnvironment); diff --git a/src/util/simple-df/dfg-view.ts b/src/util/simple-df/dfg-view.ts index f669d769706..b5ca1deb67b 100644 --- a/src/util/simple-df/dfg-view.ts +++ b/src/util/simple-df/dfg-view.ts @@ -1,9 +1,10 @@ import { DataflowGraph } from '../../dataflow/graph/graph'; -import { type DataflowGraphVertexArgument , VertexType } from '../../dataflow/graph/vertex'; -import { type MergeableRecord , deepMergeObject } from '../objects'; +import { type DataflowGraphVertexArgument, VertexType } from '../../dataflow/graph/vertex'; +import { deepMergeObject, type MergeableRecord } from '../objects'; import type { DeepPartial } from 'ts-essentials'; import type { AstIdMap } from '../../r-bridge/lang-4.x/ast/model/processing/decorate'; import { isNotUndefined } from '../assert'; +import type { REnvironmentInformation } from '../../dataflow/environments/environment'; export interface ReduceVertexOptions extends MergeableRecord { tags: VertexType[] @@ -52,7 +53,7 @@ function makeFilter(options: ReduceVertexOptions, idMap?: AstIdMap): ): DataflowGraph { +export function reduceDfg(dfg: DataflowGraph, options: DeepPartial, cleanEnv: REnvironmentInformation): DataflowGraph { const newDfg = new DataflowGraph(dfg.idMap); const applyOptions = deepMergeObject(defaultReduceOptions, options) as Required; // overwrite the tag set if possible @@ -66,7 +67,7 @@ export function reduceDfg(dfg: DataflowGraph, options: DeepPartial { - const global = initializeCleanEnvironments(); + const ctx = new FlowrAnalyzerEnvironmentContext({ config: defaultConfigOptions } as FlowrAnalyzerContext); + const global = ctx.makeCleanEnv(); return new EnvironmentBuilder(global.current, global.current.parent, 0); }; diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index 24bcaa0dd06..4508d717439 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -1,33 +1,37 @@ -import { type MergeableRecord , deepMergeObject } from '../../../src/util/objects'; +import { deepMergeObject, type MergeableRecord } from '../../../src/util/objects'; import { NAIVE_RECONSTRUCT } from '../../../src/core/steps/all/static-slicing/10-reconstruct'; import { guard, isNotUndefined } from '../../../src/util/assert'; import { PipelineExecutor } from '../../../src/core/pipeline-executor'; -import { type TestLabel, type TestLabelContext , decorateLabelContext, dropTestLabel, modifyLabelName } from './label'; +import { decorateLabelContext, dropTestLabel, modifyLabelName, type TestLabel, type TestLabelContext } from './label'; import { printAsBuilder } from './dataflow/dataflow-builder-printer'; import { RShell } from '../../../src/r-bridge/shell'; import type { NoInfo, RNode } from '../../../src/r-bridge/lang-4.x/ast/model/model'; -import { type fileProtocol, type RParseRequests } from '../../../src/r-bridge/retriever'; -import { type ParentInformation, +import { type fileProtocol, type RParseRequests } from '../../../src/r-bridge/retriever'; +import { type AstIdMap, + deterministicCountingIdGenerator, type IdGenerator, type NormalizedAst, + type ParentInformation, type RNodeWithParent - , deterministicCountingIdGenerator } from '../../../src/r-bridge/lang-4.x/ast/model/processing/decorate'; import { - type DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE, - type TREE_SITTER_SLICE_AND_RECONSTRUCT_PIPELINE - , createSlicePipeline, DEFAULT_NORMALIZE_PIPELINE, - TREE_SITTER_NORMALIZE_PIPELINE + type DEFAULT_SLICE_AND_RECONSTRUCT_PIPELINE, + TREE_SITTER_NORMALIZE_PIPELINE, + type TREE_SITTER_SLICE_AND_RECONSTRUCT_PIPELINE } from '../../../src/core/steps/pipeline/default-pipelines'; import type { RExpressionList } from '../../../src/r-bridge/lang-4.x/ast/model/nodes/r-expression-list'; import { diffOfDataflowGraphs } from '../../../src/dataflow/graph/diff-dataflow-graph'; -import { type NodeId , normalizeIdToNumberIfPossible } from '../../../src/r-bridge/lang-4.x/ast/model/processing/node-id'; +import { type NodeId, normalizeIdToNumberIfPossible } from '../../../src/r-bridge/lang-4.x/ast/model/processing/node-id'; import { type DataflowGraph } from '../../../src/dataflow/graph/graph'; import { diffGraphsToMermaidUrl, graphToMermaidUrl } from '../../../src/util/mermaid/dfg'; -import { type SingleSlicingCriterion, type SlicingCriteria , slicingCriterionToId } from '../../../src/slicing/criterion/parse'; +import { + type SingleSlicingCriterion, + type SlicingCriteria, + slicingCriterionToId +} from '../../../src/slicing/criterion/parse'; import { normalizedAstToMermaidUrl } from '../../../src/util/mermaid/ast'; import type { AutoSelectPredicate } from '../../../src/reconstruct/auto-select/auto-select-defaults'; import { resolveDataflowGraph } from '../../../src/dataflow/graph/resolve-graph'; @@ -42,8 +46,8 @@ import { resolveByName } from '../../../src/dataflow/environments/resolve-by-nam import type { GraphDifferenceReport, ProblematicDiffInfo } from '../../../src/util/diff-graph'; import { extractCfg } from '../../../src/control-flow/extract-cfg'; import { cfgToMermaidUrl } from '../../../src/util/mermaid/cfg'; -import { type CfgProperty , assertCfgSatisfiesProperties } from '../../../src/control-flow/cfg-properties'; -import { type FlowrConfigOptions , cloneConfig, defaultConfigOptions } from '../../../src/config'; +import { assertCfgSatisfiesProperties, type CfgProperty } from '../../../src/control-flow/cfg-properties'; +import { cloneConfig, defaultConfigOptions, type FlowrConfigOptions } from '../../../src/config'; import { FlowrAnalyzerBuilder } from '../../../src/project/flowr-analyzer-builder'; import type { ReadonlyFlowrAnalysisProvider } from '../../../src/project/flowr-analyzer'; import type { KnownParser } from '../../../src/r-bridge/parser'; @@ -410,7 +414,7 @@ export function assertDataflow( expected.setIdMap(normalize.idMap); if(userConfig?.resolveIdsAsCriterion) { - expected = resolveDataflowGraph(expected); + expected = resolveDataflowGraph(expected, analyzer.inspectContext()); } const report: GraphDifferenceReport = diffOfDataflowGraphs( diff --git a/test/functionality/dataflow/environments/resolve.test.ts b/test/functionality/dataflow/environments/resolve.test.ts index 535c37d566a..240125b5084 100644 --- a/test/functionality/dataflow/environments/resolve.test.ts +++ b/test/functionality/dataflow/environments/resolve.test.ts @@ -7,7 +7,7 @@ import { Ternary } from '../../../../src/util/logic'; import { assert, describe, expect, test } from 'vitest'; import { valueFromTsValue } from '../../../../src/dataflow/eval/values/general'; import { setFrom } from '../../../../src/dataflow/eval/values/sets/set-constants'; -import { type Lift, type Value , Bottom, isBottom, isTop, Top } from '../../../../src/dataflow/eval/values/r-value'; +import { Bottom, isBottom, isTop, type Lift, Top, type Value } 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'; @@ -69,9 +69,10 @@ describe.sequential('Resolve', withShell(shell => { const effectiveName = decorateLabelContext(label(name), ['resolve']); test(effectiveName, async() => { + const context = contextFromInput(code.trim()); const dataflow = await new PipelineExecutor(DEFAULT_DATAFLOW_PIPELINE, { - parser: shell, - context: contextFromInput(code.trim()), + parser: shell, + context }).allRemainingSteps(); const resolved = resolveIdToValue(slicingCriterionToId(identifier, dataflow.normalize.idMap), { @@ -79,7 +80,8 @@ describe.sequential('Resolve', withShell(shell => { graph: dataflow.dataflow.graph, idMap: dataflow.normalize.idMap, full: true, - resolve: defaultConfigOptions.solver.variables + resolve: defaultConfigOptions.solver.variables, + ctx: context }); if((allow & Allow.Top) == Allow.Top && isTop(resolved)) { diff --git a/test/functionality/dataflow/functions/higher-order-functions.test.ts b/test/functionality/dataflow/functions/higher-order-functions.test.ts index c8f53ac9c04..cbc290bab70 100644 --- a/test/functionality/dataflow/functions/higher-order-functions.test.ts +++ b/test/functionality/dataflow/functions/higher-order-functions.test.ts @@ -1,6 +1,6 @@ -import { describe, assert, test } from 'vitest'; +import { assert, describe, test } from 'vitest'; import { withTreeSitter } from '../../_helper/shell'; -import { type SingleSlicingCriterion , tryResolveSliceCriterionToId } from '../../../../src/slicing/criterion/parse'; +import { type SingleSlicingCriterion, tryResolveSliceCriterionToId } from '../../../../src/slicing/criterion/parse'; import { createDataflowPipeline } from '../../../../src/core/steps/pipeline/default-pipelines'; import { isHigherOrder } from '../../../../src/dataflow/fn/higher-order-function'; import { contextFromInput } from '../../../../src/project/context/flowr-analyzer-context'; @@ -17,15 +17,16 @@ describe('is-higher-order-function', withTreeSitter(ts => { for(const [exp, crit] of [[true, expect.pos], [false, expect.neg]] as const) { for(const c of crit ?? []) { test(`${label} (expect ${c} to be ${exp ? 'ho' : 'not ho'})`, async() => { + const context = contextFromInput(code); const df = await createDataflowPipeline(ts, { - context: contextFromInput(code) + context: context }).allRemainingSteps(); const id = tryResolveSliceCriterionToId(c, df.normalize.idMap); // move up the error message :sparkles: assert.isDefined(id, `could not resolve criterion ${c}`); - assert.strictEqual(isHigherOrder(id, df.dataflow.graph), exp); + assert.strictEqual(isHigherOrder(id, df.dataflow.graph, context), exp); }); } } diff --git a/test/functionality/dataflow/query/dependencies-query.test.ts b/test/functionality/dataflow/query/dependencies-query.test.ts index a353cad0367..c1a8edce7d8 100644 --- a/test/functionality/dataflow/query/dependencies-query.test.ts +++ b/test/functionality/dataflow/query/dependencies-query.test.ts @@ -1,11 +1,12 @@ import { assertQuery } from '../../_helper/query'; import { label } from '../../_helper/label'; -import { type SingleSlicingCriterion , slicingCriterionToId } from '../../../../src/slicing/criterion/parse'; +import { type SingleSlicingCriterion, slicingCriterionToId } from '../../../../src/slicing/criterion/parse'; import { type DependenciesQuery, type DependenciesQueryResult, - type DependencyInfo - , Unknown } from '../../../../src/queries/catalog/dependencies-query/dependencies-query-format'; + type DependencyInfo, + Unknown +} from '../../../../src/queries/catalog/dependencies-query/dependencies-query-format'; import type { AstIdMap } from '../../../../src/r-bridge/lang-4.x/ast/model/processing/decorate'; import { describe } from 'vitest'; import { withTreeSitter } from '../../_helper/shell'; diff --git a/test/functionality/dataflow/environments/initialization.test.ts b/test/functionality/project/context/flowr-analyzer-environment-context.test.ts similarity index 70% rename from test/functionality/dataflow/environments/initialization.test.ts rename to test/functionality/project/context/flowr-analyzer-environment-context.test.ts index 4d3d0e2cecc..b174634ed5f 100644 --- a/test/functionality/dataflow/environments/initialization.test.ts +++ b/test/functionality/project/context/flowr-analyzer-environment-context.test.ts @@ -1,11 +1,16 @@ import { expect } from 'chai'; import { label } from '../../_helper/label'; -import { Environment, initializeCleanEnvironments } from '../../../../src/dataflow/environments/environment'; +import { Environment } from '../../../../src/dataflow/environments/environment'; import { assert, describe, test } from 'vitest'; +import { FlowrAnalyzerEnvironmentContext } from '../../../../src/project/context/flowr-analyzer-environment-context'; +import { defaultConfigOptions } from '../../../../src/config'; +import type { FlowrAnalyzerContext } from '../../../../src/project/context/flowr-analyzer-context'; describe('Initialization', () => { + const ctx = new FlowrAnalyzerEnvironmentContext({ config: defaultConfigOptions } as FlowrAnalyzerContext); + test(label('Clean creation should have no info but the default information', ['global-scope'], ['other']), () => { - const clean = initializeCleanEnvironments(); + const clean = ctx.makeCleanEnv(); assert.isDefined(clean.current,'there should be a current environment'); expect(clean.current.memory.size, 'the current environment should have no memory').to.be.equal(0); expect(clean.level, 'the level of the clean environment is predefined as 0').to.be.equal(0); @@ -13,11 +18,11 @@ describe('Initialization', () => { expect(clean.current.parent.id, 'the ID of the parent environment is predefined as 0').to.be.equal(0); }); test(label('Clean creation should create independent new environments', ['lexicographic-scope'], ['other']), () => { - const clean = initializeCleanEnvironments(); + const clean = ctx.makeCleanEnv(); clean.current.parent = new Environment(clean.current.parent); const newParentId = clean.current.parent.id; - const second = initializeCleanEnvironments(); + const second = ctx.makeCleanEnv(); expect(second.current.parent.id, 'the new one should have a parent, the built-in environment').to.be.equal(0); assert.isDefined(clean.current.parent, 'the old one should still have the parent'); expect(clean.current.parent.id, 'parent should be unchanged').to.be.equal(newParentId); diff --git a/test/functionality/slicing/backward/slicing.bench.ts b/test/functionality/slicing/backward/slicing.bench.ts index 7ac50038c2a..7a4fe5f8574 100644 --- a/test/functionality/slicing/backward/slicing.bench.ts +++ b/test/functionality/slicing/backward/slicing.bench.ts @@ -8,10 +8,11 @@ import { SliceDirection } from '../../../../src/core/steps/all/static-slicing/00 import { FlowrAnalyzerBuilder } from '../../../../src/project/flowr-analyzer-builder'; import type { DataflowInformation } from '../../../../src/dataflow/info'; import type { NormalizedAst } from '../../../../src/r-bridge/lang-4.x/ast/model/processing/decorate'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../../src/project/context/flowr-analyzer-context'; describe('slicing', () => { - let result: { dataflow: DataflowInformation; normalize: NormalizedAst } | undefined = undefined; + let result: { dataflow: DataflowInformation; normalize: NormalizedAst, ctx: ReadOnlyFlowrAnalyzerContext } | undefined = undefined; let ids: NodeId[] | undefined = undefined; for(const threshold of [1, 10, 100, 200]) { @@ -38,11 +39,11 @@ x[2] <- x[1] + x[3] const analyzer = await new FlowrAnalyzerBuilder() .setParser(exec).build(); analyzer.addRequest(code); - result = { dataflow: await analyzer.dataflow(), normalize: await analyzer.normalize() }; + result = { dataflow: await analyzer.dataflow(), normalize: await analyzer.normalize(), ctx: analyzer.inspectContext() }; ids = (await analyzer.runSearch(Q.var('print').first())).getElements().map(n => n.node.info.id); } guard(ids !== undefined, () => 'no result'); - staticSlice(result.dataflow, result.normalize, [`$${ids[0]}`], SliceDirection.Backward, threshold); + staticSlice(result.ctx, result.dataflow, result.normalize, [`$${ids[0]}`], SliceDirection.Backward, threshold); }); } }); diff --git a/test/functionality/slicing/backward/static-backward-program-slices/alias-tracking.test.ts b/test/functionality/slicing/backward/static-backward-program-slices/alias-tracking.test.ts index 79201f99011..68e6f2e45eb 100644 --- a/test/functionality/slicing/backward/static-backward-program-slices/alias-tracking.test.ts +++ b/test/functionality/slicing/backward/static-backward-program-slices/alias-tracking.test.ts @@ -9,12 +9,13 @@ import { setFrom } from '../../../../../src/dataflow/eval/values/sets/set-consta import { valueFromTsValue } from '../../../../../src/dataflow/eval/values/general'; import { Top } from '../../../../../src/dataflow/eval/values/r-value'; import { trackAliasInEnvironments } from '../../../../../src/dataflow/eval/resolve/alias-tracking'; +import type { FlowrAnalyzerContext } from '../../../../../src/project/context/flowr-analyzer-context'; import { contextFromInput } from '../../../../../src/project/context/flowr-analyzer-context'; -async function runPipeline(code: string, shell: RShell) { +async function runPipeline(code: string, shell: RShell, ctx: FlowrAnalyzerContext) { return await new PipelineExecutor(DEFAULT_DATAFLOW_PIPELINE, { parser: shell, - context: contextFromInput(code) + context: ctx }).allRemainingSteps(); } @@ -30,11 +31,13 @@ describe.sequential('Alias Tracking', withShell(shell => { ['f <- function(a = u) { if(k) { u <- 1; } else { u <- 2; }; print(a); }; f();', 'a', Top], // Note: This should result in a in [1,2] in the future ['x <- 1; while(x < 10) { if(runif(1)) x <- x + 1 }', 'x', Top] ])('%s should resolve %s to %o', async(code, identifier, expectedValues) => { - const result = await runPipeline(code, shell); + const ctx = contextFromInput(code); + const result = await runPipeline(code, shell, ctx); const values = trackAliasInEnvironments( defaultConfigOptions.solver.variables, identifier as Identifier, result.dataflow.environment, + ctx, result.dataflow.graph, result.dataflow.graph.idMap ); diff --git a/test/functionality/slicing/slicing.bench.ts b/test/functionality/slicing/slicing.bench.ts index a5d3c4931f1..bb6c7aae294 100644 --- a/test/functionality/slicing/slicing.bench.ts +++ b/test/functionality/slicing/slicing.bench.ts @@ -9,10 +9,11 @@ import { SliceDirection } from '../../../src/core/steps/all/static-slicing/00-sl import { FlowrAnalyzerBuilder } from '../../../src/project/flowr-analyzer-builder'; import type { DataflowInformation } from '../../../src/dataflow/info'; import type { NormalizedAst } from '../../../src/r-bridge/lang-4.x/ast/model/processing/decorate'; +import type { ReadOnlyFlowrAnalyzerContext } from '../../../src/project/context/flowr-analyzer-context'; describe('slicing', () => { - let result: { dataflow: DataflowInformation; normalize: NormalizedAst } | undefined = undefined; + let result: { dataflow: DataflowInformation; normalize: NormalizedAst, ctx: ReadOnlyFlowrAnalyzerContext } | undefined = undefined; let ids: NodeId[] | undefined = undefined; for(const threshold of [1, 10, 100, 200]) { @@ -41,11 +42,11 @@ for(i in 1:5) { .setParser(exec) .build(); analyzer.addRequest(request); - result = { dataflow: await analyzer.dataflow(), normalize: await analyzer.normalize() }; + result = { dataflow: await analyzer.dataflow(), normalize: await analyzer.normalize(), ctx: analyzer.inspectContext() }; ids = (await analyzer.runSearch(Q.var('print').first())).getElements().map(n => n.node.info.id); } guard(ids !== undefined, () => 'no result'); - staticSlice(result.dataflow, result.normalize, [`$${ids[0]}`], SliceDirection.Backward, threshold); + staticSlice(result.ctx, result.dataflow, result.normalize, [`$${ids[0]}`], SliceDirection.Backward, threshold); }); } });