@eagleoutice/flowr 2.0.16 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/cli/repl/commands/commands.js +3 -1
  3. package/cli/repl/commands/lineage.d.ts +15 -0
  4. package/cli/repl/commands/lineage.js +66 -0
  5. package/cli/repl/server/connection.d.ts +1 -0
  6. package/cli/repl/server/connection.js +34 -0
  7. package/cli/repl/server/messages/lineage.d.ts +16 -0
  8. package/cli/repl/server/messages/lineage.js +17 -0
  9. package/cli/repl/server/messages/messages.d.ts +2 -1
  10. package/dataflow/environments/built-in.js +18 -8
  11. package/dataflow/environments/environment.js +4 -3
  12. package/dataflow/environments/resolve-by-name.d.ts +1 -1
  13. package/dataflow/environments/resolve-by-name.js +4 -4
  14. package/dataflow/graph/diff.js +1 -0
  15. package/dataflow/graph/graph.d.ts +18 -19
  16. package/dataflow/graph/graph.js +41 -24
  17. package/dataflow/graph/vertex.d.ts +6 -1
  18. package/dataflow/graph/vertex.js +21 -0
  19. package/dataflow/info.d.ts +1 -1
  20. package/dataflow/internal/process/functions/call/built-in/built-in-access.d.ts +2 -1
  21. package/dataflow/internal/process/functions/call/built-in/built-in-access.js +3 -3
  22. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.d.ts +2 -1
  23. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +5 -4
  24. package/dataflow/internal/process/functions/call/built-in/built-in-for-loop.js +1 -1
  25. package/dataflow/internal/process/functions/call/built-in/built-in-function-definition.js +10 -1
  26. package/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.js +4 -4
  27. package/dataflow/internal/process/functions/call/built-in/built-in-library.js +1 -0
  28. package/dataflow/internal/process/functions/call/built-in/built-in-quote.d.ts +3 -2
  29. package/dataflow/internal/process/functions/call/built-in/built-in-quote.js +1 -1
  30. package/dataflow/internal/process/functions/call/built-in/built-in-replacement.d.ts +3 -2
  31. package/dataflow/internal/process/functions/call/built-in/built-in-replacement.js +2 -1
  32. package/dataflow/internal/process/functions/call/built-in/built-in-source.d.ts +2 -2
  33. package/dataflow/internal/process/functions/call/built-in/built-in-source.js +18 -7
  34. package/dataflow/internal/process/functions/call/built-in/{built-in-logical-bin-op.d.ts → built-in-special-bin-op.d.ts} +2 -1
  35. package/dataflow/internal/process/functions/call/built-in/{built-in-logical-bin-op.js → built-in-special-bin-op.js} +3 -3
  36. package/dataflow/internal/process/functions/call/common.d.ts +6 -2
  37. package/dataflow/internal/process/functions/call/common.js +36 -1
  38. package/dataflow/internal/process/functions/call/known-call-handling.d.ts +3 -2
  39. package/dataflow/internal/process/functions/call/known-call-handling.js +2 -2
  40. package/dataflow/internal/process/functions/call/named-call-handling.js +3 -1
  41. package/dataflow/internal/process/functions/call/unnamed-call-handling.js +1 -0
  42. package/dataflow/internal/process/functions/process-argument.js +0 -28
  43. package/package.json +3 -2
  44. package/r-bridge/data/data.d.ts +5 -0
  45. package/r-bridge/data/data.js +6 -0
  46. package/slicing/criterion/parse.d.ts +3 -4
  47. package/slicing/criterion/parse.js +3 -3
  48. package/slicing/static/static-slicer.d.ts +1 -1
  49. package/slicing/static/static-slicer.js +10 -4
  50. package/statistics/statistics.js +1 -1
  51. package/util/json.d.ts +1 -1
  52. package/util/json.js +3 -3
  53. package/util/logic.d.ts +5 -1
  54. package/util/version.js +1 -1
  55. package/abstract-interpretation/domain.d.ts +0 -57
  56. package/abstract-interpretation/domain.js +0 -176
  57. package/abstract-interpretation/handler/binop/binop.d.ts +0 -15
  58. package/abstract-interpretation/handler/binop/binop.js +0 -42
  59. package/abstract-interpretation/handler/binop/operators.d.ts +0 -2
  60. package/abstract-interpretation/handler/binop/operators.js +0 -28
  61. package/abstract-interpretation/handler/handler.d.ts +0 -6
  62. package/abstract-interpretation/handler/handler.js +0 -3
  63. package/abstract-interpretation/processor.d.ts +0 -11
  64. package/abstract-interpretation/processor.js +0 -84
@@ -57,7 +57,7 @@ export interface DataflowInformation extends DataflowCfgInformation {
57
57
  graph: DataflowGraph;
58
58
  }
59
59
  export declare function initializeCleanDataflowInformation<T>(entryPoint: NodeId, data: Pick<DataflowProcessorInformation<T>, 'environment' | 'completeAst'>): DataflowInformation;
60
- export declare function happensInEveryBranch(controlDependencies: ControlDependency[] | undefined): boolean;
60
+ export declare function happensInEveryBranch(controlDependencies: readonly ControlDependency[] | undefined): boolean;
61
61
  export declare function alwaysExits(data: DataflowInformation): boolean;
62
62
  export declare function filterOutLoopExitPoints(exitPoints: readonly ExitPoint[]): readonly ExitPoint[];
63
63
  export declare function diffControlDependency<Report extends WriteableDifferenceReport>(a: ControlDependency | undefined, b: ControlDependency | undefined, info: GenericDifferenceInformation<Report>): void;
@@ -4,6 +4,7 @@ import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/
4
4
  import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
5
5
  import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
6
6
  import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
7
+ import type { ForceArguments } from '../common';
7
8
  export declare function processAccess<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, config: {
8
9
  treatIndicesAsString: boolean;
9
- }): DataflowInformation;
10
+ } & ForceArguments): DataflowInformation;
@@ -9,13 +9,13 @@ const environment_1 = require("../../../../../environments/environment");
9
9
  function processAccess(name, args, rootId, data, config) {
10
10
  if (args.length < 2) {
11
11
  logger_1.dataflowLogger.warn(`Access ${name.content} has less than 2 arguments, skipping`);
12
- return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
12
+ return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs }).information;
13
13
  }
14
14
  const head = args[0];
15
15
  (0, assert_1.guard)(head !== r_function_call_1.EmptyArgument, () => `Access ${name.content} has no source, impossible!`);
16
16
  let fnCall;
17
17
  if (!config.treatIndicesAsString) {
18
- fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data });
18
+ fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs });
19
19
  }
20
20
  else {
21
21
  const newArgs = [...args];
@@ -38,7 +38,7 @@ function processAccess(name, args, rootId, data, config) {
38
38
  };
39
39
  }
40
40
  }
41
- fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: newArgs, rootId, data });
41
+ fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: newArgs, rootId, data, forceArgs: config.forceArgs });
42
42
  }
43
43
  const info = fnCall.information;
44
44
  info.graph.addEdge(name.info.id, fnCall.processedArguments[0]?.entryPoint ?? head.info.id, { type: 8 /* EdgeType.Returns */ });
@@ -5,7 +5,8 @@ import type { RNode } from '../../../../../../r-bridge/lang-4.x/ast/model/model'
5
5
  import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
6
6
  import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
7
7
  import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
8
- export interface AssignmentConfiguration {
8
+ import type { ForceArguments } from '../common';
9
+ export interface AssignmentConfiguration extends ForceArguments {
9
10
  readonly superAssignment?: boolean;
10
11
  readonly swapSourceAndTarget?: boolean;
11
12
  readonly makeMaybe?: boolean;
@@ -35,13 +35,13 @@ function processAssignment(name,
35
35
  args, rootId, data, config) {
36
36
  if (args.length != 2) {
37
37
  logger_1.dataflowLogger.warn(`Assignment ${name.content} has something else than 2 arguments, skipping`);
38
- return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
38
+ return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs }).information;
39
39
  }
40
40
  const effectiveArgs = getEffectiveOrder(config, args);
41
41
  const { target, source } = extractSourceAndTarget(effectiveArgs, name);
42
42
  const { type, named } = target;
43
43
  if (type === "RSymbol" /* RType.Symbol */) {
44
- const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, reverseOrder: !config.swapSourceAndTarget });
44
+ const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, reverseOrder: !config.swapSourceAndTarget, forceArgs: config.forceArgs });
45
45
  return processAssignmentToSymbol({
46
46
  ...config,
47
47
  name,
@@ -68,7 +68,7 @@ args, rootId, data, config) {
68
68
  return processAssignmentToString(target, args, name, rootId, data, config, source);
69
69
  }
70
70
  logger_1.dataflowLogger.warn(`Assignment ${name.content} has an unknown target type ${target.type}, skipping`);
71
- return (0, known_call_handling_1.processKnownFunctionCall)({ name, args: effectiveArgs, rootId, data }).information;
71
+ return (0, known_call_handling_1.processKnownFunctionCall)({ name, args: effectiveArgs, rootId, data, forceArgs: config.forceArgs }).information;
72
72
  }
73
73
  exports.processAssignment = processAssignment;
74
74
  function extractSourceAndTarget(args, name) {
@@ -105,7 +105,8 @@ function processAssignmentToString(target, args, name, rootId, data, config, sou
105
105
  args: mappedArgs,
106
106
  rootId,
107
107
  data,
108
- reverseOrder: !config.swapSourceAndTarget
108
+ reverseOrder: !config.swapSourceAndTarget,
109
+ forceArgs: config.forceArgs
109
110
  });
110
111
  return processAssignmentToSymbol({
111
112
  ...config,
@@ -63,7 +63,7 @@ function processForLoop(name, args, rootId, data) {
63
63
  for (const readId of readIdsToLink) {
64
64
  nextGraph.addEdge(readId.nodeId, write.nodeId, { type: 1 /* EdgeType.Reads */ });
65
65
  }
66
- // now, we remove the name from the id shares as they are no longer needed
66
+ // now, we remove the name from the id shares as they are no longer necessary
67
67
  nameIdShares.delete(name);
68
68
  nextGraph.setDefinitionOfVertex(write);
69
69
  }
@@ -45,8 +45,17 @@ function processFunctionDefinition(name, args, rootId, data) {
45
45
  const readInBody = [...body.in, ...body.unknownReferences];
46
46
  // there is no uncertainty regarding the arguments, as if a function header is executed, so is its body
47
47
  const remainingRead = (0, linker_1.linkInputs)(readInBody, paramsEnvironments, readInParameters.slice(), body.graph, true /* functions do not have to be called */);
48
+ // functions can be called multiple times,
49
+ // so if they have a global effect, we have to link them as if they would be executed a loop
50
+ /* theoretically, we should just check if there is a global effect-write somewhere within */
51
+ if (remainingRead.length > 0) {
52
+ const nameIdShares = (0, linker_1.produceNameSharedIdMap)(remainingRead);
53
+ const definedInLocalEnvironment = new Set([...bodyEnvironment.current.memory.values()].flat().map(d => d.nodeId));
54
+ // Everything that is in body.out but not within the local environment populated for the function scope is a potential escape ~> global definition
55
+ const globalBodyOut = body.out.filter(d => !definedInLocalEnvironment.has(d.nodeId));
56
+ (0, linker_1.linkCircularRedefinitionsWithinALoop)(body.graph, nameIdShares, globalBodyOut);
57
+ }
48
58
  subgraph.mergeWith(body.graph);
49
- logger_1.dataflowLogger.trace(`Function definition with id ${name.info.id} has ${remainingRead.length} remaining reads`);
50
59
  const outEnvironment = (0, overwrite_1.overwriteEnvironment)(paramsEnvironments, bodyEnvironment);
51
60
  for (const read of remainingRead) {
52
61
  if (read.name) {
@@ -33,23 +33,23 @@ function processIfThenElse(name, args, rootId, data) {
33
33
  // we should defer this to the abstract interpretation
34
34
  const conditionIsFalse = (0, resolve_by_name_1.resolvesToBuiltInConstant)(condArg?.lexeme, data.environment, false);
35
35
  const conditionIsTrue = (0, resolve_by_name_1.resolvesToBuiltInConstant)(condArg?.lexeme, data.environment, true);
36
- if (conditionIsFalse !== 'always') {
36
+ if (conditionIsFalse !== 0 /* Ternary.Always */) {
37
37
  then = (0, processor_1.processDataflowFor)(thenArg, data);
38
38
  if (then.entryPoint) {
39
39
  then.graph.addEdge(name.info.id, then.entryPoint, { type: 8 /* EdgeType.Returns */ });
40
40
  }
41
- if (conditionIsTrue !== 'always') {
41
+ if (conditionIsTrue !== 0 /* Ternary.Always */) {
42
42
  makeThenMaybe = true;
43
43
  }
44
44
  }
45
45
  let otherwise;
46
46
  let makeOtherwiseMaybe = false;
47
- if (otherwiseArg !== undefined && conditionIsTrue !== 'always') {
47
+ if (otherwiseArg !== undefined && conditionIsTrue !== 0 /* Ternary.Always */) {
48
48
  otherwise = (0, processor_1.processDataflowFor)(otherwiseArg, data);
49
49
  if (otherwise.entryPoint) {
50
50
  otherwise.graph.addEdge(name.info.id, otherwise.entryPoint, { type: 8 /* EdgeType.Returns */ });
51
51
  }
52
- if (conditionIsFalse !== 'always') {
52
+ if (conditionIsFalse !== 0 /* Ternary.Always */) {
53
53
  makeOtherwiseMaybe = true;
54
54
  }
55
55
  }
@@ -5,6 +5,7 @@ const known_call_handling_1 = require("../known-call-handling");
5
5
  const logger_1 = require("../../../../../logger");
6
6
  const unpack_argument_1 = require("../argument/unpack-argument");
7
7
  const make_argument_1 = require("../argument/make-argument");
8
+ /* we currently do not mark this as an unknown side effect, as we can enable/disable this with a toggle */
8
9
  function processLibrary(name, args, rootId, data) {
9
10
  if (args.length !== 1) {
10
11
  logger_1.dataflowLogger.warn(`Currently only one-arg library-likes are allows (for ${name.content}), skipping`);
@@ -4,6 +4,7 @@ import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/
4
4
  import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
5
5
  import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
6
6
  import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
7
- export declare function processQuote<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, config?: {
7
+ import type { ForceArguments } from '../common';
8
+ export declare function processQuote<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, config: {
8
9
  quoteArgumentsWithIndex?: number;
9
- }): DataflowInformation;
10
+ } & ForceArguments): DataflowInformation;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.processQuote = void 0;
4
4
  const known_call_handling_1 = require("../known-call-handling");
5
5
  function processQuote(name, args, rootId, data, config) {
6
- const { information, processedArguments, fnRef } = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data });
6
+ const { information, processedArguments, fnRef } = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs });
7
7
  const inRefs = [fnRef];
8
8
  const outRefs = [];
9
9
  const unknownRefs = [];
@@ -1,12 +1,13 @@
1
1
  import type { DataflowProcessorInformation } from '../../../../../processor';
2
2
  import type { DataflowInformation } from '../../../../../info';
3
+ import type { ForceArguments } from '../common';
3
4
  import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
4
5
  import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
5
6
  import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
6
7
  import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
7
8
  export declare function processReplacementFunction<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>,
8
- /** last one has to be the value */
9
+ /** The last one has to be the value */
9
10
  args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, config: {
10
11
  makeMaybe?: boolean;
11
12
  assignmentOperator?: '<-' | '<<-';
12
- }): DataflowInformation;
13
+ } & ForceArguments): DataflowInformation;
@@ -11,7 +11,7 @@ const logger_1 = require("../../../../../logger");
11
11
  const graph_1 = require("../../../../../graph/graph");
12
12
  const dfg_1 = require("../../../../../../util/mermaid/dfg");
13
13
  function processReplacementFunction(name,
14
- /** last one has to be the value */
14
+ /** The last one has to be the value */
15
15
  args, rootId, data, config) {
16
16
  if (args.length < 2) {
17
17
  logger_1.dataflowLogger.warn(`Replacement ${name.content} has less than 2 arguments, skipping`);
@@ -28,6 +28,7 @@ args, rootId, data, config) {
28
28
  data,
29
29
  functionRootId: rootId,
30
30
  finalGraph: res.graph,
31
+ forceArgs: config.forceArgs,
31
32
  });
32
33
  const fn = res.graph.getVertex(rootId, true);
33
34
  (0, assert_1.guard)(fn?.tag === "function-call" /* VertexType.FunctionCall */ && fn.args.length === 2, () => `Function ${rootId} not found in graph or not 2-arg fn-call (${JSON.stringify(fn)}) ${(0, dfg_1.graphToMermaidUrl)(res.graph)}`);
@@ -10,8 +10,8 @@ export declare function setSourceProvider(provider: RParseRequestProvider): void
10
10
  export declare function processSourceCall<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, config: {
11
11
  /** should this produce an explicit source function call in the graph? */
12
12
  includeFunctionCall?: boolean;
13
- /** should this function call be followed, even when the configuratio disables it? */
13
+ /** should this function call be followed, even when the configuration disables it? */
14
14
  forceFollow?: boolean;
15
15
  }): DataflowInformation;
16
- export declare function sourceRequest<OtherInfo>(request: RParseRequest, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, information: DataflowInformation, getId: IdGenerator<NoInfo>): DataflowInformation;
16
+ export declare function sourceRequest<OtherInfo>(rootId: NodeId, request: RParseRequest, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, information: DataflowInformation, getId: IdGenerator<NoInfo>): DataflowInformation;
17
17
  export declare function standaloneSourceFile<OtherInfo>(inputRequest: RParseRequest, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, uniqueSourceId: string, information: DataflowInformation): DataflowInformation;
@@ -12,6 +12,7 @@ const decorate_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/proces
12
12
  const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
13
13
  const logger_1 = require("../../../../../logger");
14
14
  const overwrite_1 = require("../../../../../environments/overwrite");
15
+ const log_1 = require("../../../../../../util/log");
15
16
  let sourceProvider = (0, retriever_1.requestProviderFromFile)();
16
17
  function setSourceProvider(provider) {
17
18
  sourceProvider = provider;
@@ -23,26 +24,29 @@ function processSourceCall(name, args, rootId, data, config) {
23
24
  : (0, info_1.initializeCleanDataflowInformation)(rootId, data);
24
25
  const sourceFile = args[0];
25
26
  if (!config.forceFollow && (0, config_1.getConfig)().ignoreSourceCalls) {
26
- logger_1.dataflowLogger.info(`Skipping source call ${JSON.stringify(sourceFile)} (disabled in config file)`);
27
+ (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `Skipping source call ${JSON.stringify(sourceFile)} (disabled in config file)`);
28
+ information.graph.markIdForUnknownSideEffects(rootId);
27
29
  return information;
28
30
  }
29
- if (sourceFile !== r_function_call_1.EmptyArgument && sourceFile?.value?.type == "RString" /* RType.String */) {
31
+ if (sourceFile !== r_function_call_1.EmptyArgument && sourceFile?.value?.type === "RString" /* RType.String */) {
30
32
  const path = (0, retriever_1.removeRQuotes)(sourceFile.lexeme);
31
33
  const request = sourceProvider.createRequest(path);
32
34
  // check if the sourced file has already been dataflow analyzed, and if so, skip it
33
35
  if (data.referenceChain.includes((0, retriever_1.requestFingerprint)(request))) {
34
- logger_1.dataflowLogger.info(`Found loop in dataflow analysis for ${JSON.stringify(request)}: ${JSON.stringify(data.referenceChain)}, skipping further dataflow analysis`);
36
+ (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `Found loop in dataflow analysis for ${JSON.stringify(request)}: ${JSON.stringify(data.referenceChain)}, skipping further dataflow analysis`);
37
+ information.graph.markIdForUnknownSideEffects(rootId);
35
38
  return information;
36
39
  }
37
- return sourceRequest(request, data, information, (0, decorate_1.sourcedDeterministicCountingIdGenerator)(path, name.location));
40
+ return sourceRequest(rootId, request, data, information, (0, decorate_1.sourcedDeterministicCountingIdGenerator)(path, name.location));
38
41
  }
39
42
  else {
40
- logger_1.dataflowLogger.info(`Non-constant argument ${JSON.stringify(sourceFile)} for source is currently not supported, skipping`);
43
+ (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `Non-constant argument ${JSON.stringify(sourceFile)} for source is currently not supported, skipping`);
44
+ information.graph.markIdForUnknownSideEffects(rootId);
41
45
  return information;
42
46
  }
43
47
  }
44
48
  exports.processSourceCall = processSourceCall;
45
- function sourceRequest(request, data, information, getId) {
49
+ function sourceRequest(rootId, request, data, information, getId) {
46
50
  const executor = new shell_executor_1.RShellExecutor();
47
51
  // parse, normalize and dataflow the sourced file
48
52
  let normalized;
@@ -59,8 +63,14 @@ function sourceRequest(request, data, information, getId) {
59
63
  }
60
64
  catch (e) {
61
65
  logger_1.dataflowLogger.warn(`Failed to analyze sourced file ${JSON.stringify(request)}, skipping: ${e.message}`);
66
+ information.graph.markIdForUnknownSideEffects(rootId);
62
67
  return information;
63
68
  }
69
+ // take the entry point as well as all the written references, and give them a control dependency to the source call to show that they are conditional
70
+ dataflow.graph.addControlDependency(dataflow.entryPoint, rootId);
71
+ for (const out of dataflow.out) {
72
+ dataflow.graph.addControlDependency(out.nodeId, rootId);
73
+ }
64
74
  // update our graph with the sourced file's information
65
75
  const newInformation = { ...information };
66
76
  newInformation.environment = (0, overwrite_1.overwriteEnvironment)(information.environment, dataflow.environment);
@@ -80,9 +90,10 @@ function standaloneSourceFile(inputRequest, data, uniqueSourceId, information) {
80
90
  // check if the sourced file has already been dataflow analyzed, and if so, skip it
81
91
  if (data.referenceChain.includes(fingerprint)) {
82
92
  logger_1.dataflowLogger.info(`Found loop in dataflow analysis for ${JSON.stringify(request)}: ${JSON.stringify(data.referenceChain)}, skipping further dataflow analysis`);
93
+ information.graph.markIdForUnknownSideEffects(uniqueSourceId);
83
94
  return information;
84
95
  }
85
- return sourceRequest(request, {
96
+ return sourceRequest(uniqueSourceId, request, {
86
97
  ...data,
87
98
  currentRequest: request,
88
99
  environment: information.environment,
@@ -4,7 +4,8 @@ import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/
4
4
  import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
5
5
  import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
6
6
  import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
7
+ import type { ForceArguments } from '../common';
7
8
  export declare function processSpecialBinOp<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, config: {
8
9
  lazy: boolean;
9
10
  evalRhsWhen: boolean;
10
- }): DataflowInformation;
11
+ } & ForceArguments): DataflowInformation;
@@ -9,9 +9,9 @@ function processSpecialBinOp(name, args, rootId, data, config) {
9
9
  }
10
10
  else if (args.length != 2) {
11
11
  logger_1.dataflowLogger.warn(`Logical bin-op ${name.content} has something else than 2 arguments, skipping`);
12
- return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
12
+ return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs }).information;
13
13
  }
14
- const { information, processedArguments } = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data,
14
+ const { information, processedArguments } = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs,
15
15
  patchData: (d, i) => {
16
16
  if (i === 1) {
17
17
  return { ...d, controlDependencies: [...d.controlDependencies ?? [], { id: name.info.id, when: config.evalRhsWhen }] };
@@ -31,4 +31,4 @@ function processSpecialBinOp(name, args, rootId, data, config) {
31
31
  return information;
32
32
  }
33
33
  exports.processSpecialBinOp = processSpecialBinOp;
34
- //# sourceMappingURL=built-in-logical-bin-op.js.map
34
+ //# sourceMappingURL=built-in-special-bin-op.js.map
@@ -8,7 +8,11 @@ import type { NodeId } from '../../../../../r-bridge/lang-4.x/ast/model/processi
8
8
  import type { REnvironmentInformation } from '../../../../environments/environment';
9
9
  import type { IdentifierReference } from '../../../../environments/identifier';
10
10
  import type { RSymbol } from '../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
11
- export interface ProcessAllArgumentInput<OtherInfo> {
11
+ export interface ForceArguments {
12
+ /** which of the arguments should be forced? this may be all, e.g., if the function itself is unknown on encounter */
13
+ readonly forceArgs?: 'all' | readonly boolean[];
14
+ }
15
+ export interface ProcessAllArgumentInput<OtherInfo> extends ForceArguments {
12
16
  readonly functionName: DataflowInformation;
13
17
  readonly args: readonly (RNode<OtherInfo & ParentInformation> | RFunctionArgument<OtherInfo & ParentInformation>)[];
14
18
  readonly data: DataflowProcessorInformation<OtherInfo & ParentInformation>;
@@ -24,7 +28,7 @@ export interface ProcessAllArgumentResult {
24
28
  readonly remainingReadInArgs: IdentifierReference[];
25
29
  readonly processedArguments: (DataflowInformation | undefined)[];
26
30
  }
27
- export declare function processAllArguments<OtherInfo>({ functionName, args, data, finalGraph, functionRootId, patchData }: ProcessAllArgumentInput<OtherInfo>): ProcessAllArgumentResult;
31
+ export declare function processAllArguments<OtherInfo>({ functionName, args, data, finalGraph, functionRootId, forceArgs, patchData }: ProcessAllArgumentInput<OtherInfo>): ProcessAllArgumentResult;
28
32
  export interface PatchFunctionCallInput<OtherInfo> {
29
33
  readonly nextGraph: DataflowGraph;
30
34
  readonly rootId: NodeId;
@@ -5,7 +5,39 @@ const processor_1 = require("../../../../processor");
5
5
  const r_function_call_1 = require("../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
6
6
  const overwrite_1 = require("../../../../environments/overwrite");
7
7
  const resolve_by_name_1 = require("../../../../environments/resolve-by-name");
8
- function processAllArguments({ functionName, args, data, finalGraph, functionRootId, patchData = d => d }) {
8
+ const vertex_1 = require("../../../../graph/vertex");
9
+ function forceVertexArgumentValueReferences(rootId, value, graph, env) {
10
+ const valueVertex = graph.getVertex(value.entryPoint);
11
+ if (!valueVertex) {
12
+ return;
13
+ }
14
+ // link read if it is function definition directly and reference the exit point
15
+ if (valueVertex.tag !== "value" /* VertexType.Value */) {
16
+ if (valueVertex.tag === "function-definition" /* VertexType.FunctionDefinition */) {
17
+ for (const exit of valueVertex.exitPoints) {
18
+ graph.addEdge(rootId, exit, { type: 1 /* EdgeType.Reads */ });
19
+ }
20
+ }
21
+ else {
22
+ for (const exit of value.exitPoints) {
23
+ graph.addEdge(rootId, exit.nodeId, { type: 1 /* EdgeType.Reads */ });
24
+ }
25
+ }
26
+ }
27
+ const containedSubflowIn = [...graph.vertices(true)]
28
+ .filter(([, info]) => (0, vertex_1.isFunctionDefinitionVertex)(info))
29
+ .flatMap(([, info]) => info);
30
+ // try to resolve them against the current environment
31
+ for (const ref of [...value.in, ...containedSubflowIn.flatMap(n => n.subflow.in)]) {
32
+ if (ref.name) {
33
+ const resolved = (0, resolve_by_name_1.resolveByName)(ref.name, env) ?? [];
34
+ for (const resolve of resolved) {
35
+ graph.addEdge(ref.nodeId, resolve.nodeId, { type: 1 /* EdgeType.Reads */ });
36
+ }
37
+ }
38
+ }
39
+ }
40
+ function processAllArguments({ functionName, args, data, finalGraph, functionRootId, forceArgs = [], patchData = d => d }) {
9
41
  let finalEnv = functionName.environment;
10
42
  // arg env contains the environments with other args defined
11
43
  let argEnv = functionName.environment;
@@ -22,6 +54,9 @@ function processAllArguments({ functionName, args, data, finalGraph, functionRoo
22
54
  continue;
23
55
  }
24
56
  const processed = (0, processor_1.processDataflowFor)(arg, { ...data, environment: argEnv });
57
+ if (arg.type === "RArgument" /* RType.Argument */ && arg.value && (forceArgs === 'all' || forceArgs[i]) && arg.value.type !== "RNumber" /* RType.Number */ && arg.value.type !== "RString" /* RType.String */ && arg.value.type !== "RLogical" /* RType.Logical */) {
58
+ forceVertexArgumentValueReferences(functionRootId, processed, processed.graph, argEnv);
59
+ }
25
60
  processedArguments.push(processed);
26
61
  finalEnv = (0, overwrite_1.overwriteEnvironment)(finalEnv, processed.environment);
27
62
  // resolve reads within argument, we resolve before adding the `processed.environment` to avoid cyclic dependencies
@@ -1,5 +1,6 @@
1
1
  import type { DataflowProcessorInformation } from '../../../../processor';
2
2
  import type { DataflowInformation } from '../../../../info';
3
+ import type { ForceArguments } from './common';
3
4
  import type { RSymbol } from '../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
4
5
  import type { ParentInformation } from '../../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
5
6
  import type { RFunctionArgument } from '../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
@@ -7,7 +8,7 @@ import type { NodeId } from '../../../../../r-bridge/lang-4.x/ast/model/processi
7
8
  import type { RNode } from '../../../../../r-bridge/lang-4.x/ast/model/model';
8
9
  import type { IdentifierReference } from '../../../../environments/identifier';
9
10
  import { DataflowGraph } from '../../../../graph/graph';
10
- export interface ProcessKnownFunctionCallInput<OtherInfo> {
11
+ export interface ProcessKnownFunctionCallInput<OtherInfo> extends ForceArguments {
11
12
  readonly name: RSymbol<OtherInfo & ParentInformation>;
12
13
  readonly args: readonly (RNode<OtherInfo & ParentInformation> | RFunctionArgument<OtherInfo & ParentInformation>)[];
13
14
  readonly rootId: NodeId;
@@ -23,4 +24,4 @@ export interface ProcessKnownFunctionCallResult {
23
24
  readonly fnRef: IdentifierReference;
24
25
  }
25
26
  export declare function markNonStandardEvaluationEdges(markAsNSE: readonly number[] | undefined, callArgs: readonly (DataflowInformation | undefined)[], finalGraph: DataflowGraph, rootId: NodeId): void;
26
- export declare function processKnownFunctionCall<OtherInfo>({ name, args, rootId, data, reverseOrder, markAsNSE, patchData }: ProcessKnownFunctionCallInput<OtherInfo>): ProcessKnownFunctionCallResult;
27
+ export declare function processKnownFunctionCall<OtherInfo>({ name, args, rootId, data, reverseOrder, markAsNSE, forceArgs, patchData }: ProcessKnownFunctionCallInput<OtherInfo>): ProcessKnownFunctionCallResult;
@@ -22,13 +22,13 @@ function markNonStandardEvaluationEdges(markAsNSE, callArgs, finalGraph, rootId)
22
22
  }
23
23
  }
24
24
  exports.markNonStandardEvaluationEdges = markNonStandardEvaluationEdges;
25
- function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = false, markAsNSE = undefined, patchData = d => d }) {
25
+ function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = false, markAsNSE = undefined, forceArgs, patchData = d => d }) {
26
26
  const functionName = (0, processor_1.processDataflowFor)(name, data);
27
27
  const finalGraph = new graph_1.DataflowGraph(data.completeAst.idMap);
28
28
  const functionCallName = name.content;
29
29
  logger_1.dataflowLogger.debug(`Using ${rootId} (name: ${functionCallName}) as root for the named function call`);
30
30
  const processArgs = reverseOrder ? [...args].reverse() : args;
31
- const { finalEnv, callArgs, remainingReadInArgs, processedArguments } = (0, common_1.processAllArguments)({ functionName, args: processArgs, data, finalGraph, functionRootId: rootId, patchData });
31
+ const { finalEnv, callArgs, remainingReadInArgs, processedArguments } = (0, common_1.processAllArguments)({ functionName, args: processArgs, data, finalGraph, functionRootId: rootId, patchData, forceArgs });
32
32
  markNonStandardEvaluationEdges(markAsNSE, processedArguments, finalGraph, rootId);
33
33
  finalGraph.addVertex({
34
34
  tag: "function-call" /* VertexType.FunctionCall */,
@@ -20,7 +20,9 @@ function mergeInformation(info, newInfo) {
20
20
  };
21
21
  }
22
22
  function processDefaultFunctionProcessor(information, name, args, rootId, data) {
23
- const call = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data });
23
+ const resolve = (0, resolve_by_name_1.resolveByName)(name.content, data.environment);
24
+ /* if we do not know where we land, we force! */
25
+ const call = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: (resolve?.length ?? 0) > 0 ? undefined : 'all' });
24
26
  return mergeInformation(information, call.information);
25
27
  }
26
28
  function processNamedCall(name, args, rootId, data) {
@@ -25,6 +25,7 @@ function processUnnamedFunctionCall(functionCall, data) {
25
25
  data,
26
26
  finalGraph,
27
27
  functionRootId
28
+ /* we know the call is right there and fully resolved, there is no need to artificially force arguments as we identify them within the subtree */
28
29
  });
29
30
  finalGraph.addVertex({
30
31
  tag: "function-call" /* VertexType.FunctionCall */,
@@ -4,8 +4,6 @@ exports.processFunctionArgument = exports.linkReadsForArgument = void 0;
4
4
  const processor_1 = require("../../../processor");
5
5
  const collect_1 = require("../../../../r-bridge/lang-4.x/ast/model/collect");
6
6
  const graph_1 = require("../../../graph/graph");
7
- const edge_1 = require("../../../graph/edge");
8
- const resolve_by_name_1 = require("../../../environments/resolve-by-name");
9
7
  function linkReadsForArgument(root, ingoingRefs, graph) {
10
8
  const allIdsBeforeArguments = new Set((0, collect_1.collectAllIds)(root, n => n.type === "RArgument" /* RType.Argument */ && n.info.id !== root.info.id));
11
9
  const ingoingBeforeArgs = ingoingRefs.filter(r => allIdsBeforeArguments.has(r.nodeId));
@@ -15,18 +13,6 @@ function linkReadsForArgument(root, ingoingRefs, graph) {
15
13
  }
16
14
  }
17
15
  exports.linkReadsForArgument = linkReadsForArgument;
18
- function hasNoOutgoingCallEdge(graph, id) {
19
- const outgoings = graph.outgoingEdges(id);
20
- if (outgoings === undefined) {
21
- return true;
22
- }
23
- for (const [_, edge] of outgoings) {
24
- if ((0, edge_1.edgeIncludesType)(edge.types, 4 /* EdgeType.Calls */)) {
25
- return false;
26
- }
27
- }
28
- return true;
29
- }
30
16
  function processFunctionArgument(argument, data) {
31
17
  const name = argument.name === undefined ? undefined : (0, processor_1.processDataflowFor)(argument.name, data);
32
18
  const value = argument.value === undefined ? undefined : (0, processor_1.processDataflowFor)(argument.value, data);
@@ -43,20 +29,6 @@ function processFunctionArgument(argument, data) {
43
29
  entryPoint = argument.info.id;
44
30
  }
45
31
  const ingoingRefs = [...value?.unknownReferences ?? [], ...value?.in ?? [], ...(name === undefined ? [] : [...name.in])];
46
- /* potentially link all function calls here (as maybes with a maybe cd) as the called function may employ calling-env-semantics if unknown */
47
- if (value) {
48
- const functionCalls = [...graph.vertices(true)]
49
- .filter(([_, info]) => info.tag === "function-call" /* VertexType.FunctionCall */)
50
- .filter(([id]) => hasNoOutgoingCallEdge(graph, id));
51
- // try to resolve them against the current environment
52
- for (const [id, info] of functionCalls) {
53
- const resolved = (0, resolve_by_name_1.resolveByName)(info.name, data.environment) ?? [];
54
- /* first, only link a read ref */
55
- for (const resolve of resolved) {
56
- graph.addEdge(id, resolve.nodeId, { type: 1 /* EdgeType.Reads */ });
57
- }
58
- }
59
- }
60
32
  if (entryPoint && argument.value?.type === "RFunctionDefinition" /* RType.FunctionDefinition */) {
61
33
  graph.addEdge(entryPoint, argument.value.info.id, { type: 1 /* EdgeType.Reads */ });
62
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagleoutice/flowr",
3
- "version": "2.0.16",
3
+ "version": "2.0.17",
4
4
  "description": "Static Dataflow Analyzer and Program Slicer for the R Programming Language",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "repository": {
@@ -33,7 +33,8 @@
33
33
  "doc": "typedoc",
34
34
  "test": "nyc --no-clean mocha",
35
35
  "performance-test": "func() { cd test/performance/ && bash run-all-suites.sh $1 $2 $3; cd ../../; }; func",
36
- "test-full": "npm run test -- --test-installation"
36
+ "test-full": "npm run test -- --test-installation",
37
+ "detect-circular-deps": "npx madge --extensions ts,tsx --circular src/"
37
38
  },
38
39
  "keywords": [
39
40
  "static code analysis",
@@ -146,6 +146,11 @@ export declare const flowrCapabilities: {
146
146
  readonly id: "redefinition-of-built-in-functions-primitives";
147
147
  readonly supported: "partially";
148
148
  readonly description: "_Handle cases like `print <- function(x) x`, `` `for` <- function(a,b,c) a``, ..._ Currently, we can not handle all of them there are no tests. Still wip as part of desugaring";
149
+ }, {
150
+ readonly name: "Functions with global side effects";
151
+ readonly id: "functions-with-global-side-effects";
152
+ readonly supported: "partially";
153
+ readonly description: "_Support functions like `setwd` which have an impact on the subsequent program._";
149
154
  }, {
150
155
  readonly name: "Index Access";
151
156
  readonly id: "index-access";
@@ -184,6 +184,12 @@ exports.flowrCapabilities = {
184
184
  supported: 'partially',
185
185
  description: '_Handle cases like `print <- function(x) x`, `` `for` <- function(a,b,c) a``, ..._ Currently, we can not handle all of them there are no tests. Still wip as part of desugaring'
186
186
  },
187
+ {
188
+ name: 'Functions with global side effects',
189
+ id: 'functions-with-global-side-effects',
190
+ supported: 'partially',
191
+ description: '_Support functions like `setwd` which have an impact on the subsequent program._'
192
+ },
187
193
  {
188
194
  name: 'Index Access',
189
195
  id: 'index-access',
@@ -1,6 +1,5 @@
1
- import type { NoInfo } from '../../r-bridge/lang-4.x/ast/model/model';
2
1
  import { type NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
3
- import type { NormalizedAst, ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
2
+ import type { AstIdMap } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
4
3
  /** Either `line:column`, `line@variable-name`, or `$id` */
5
4
  export type SingleSlicingCriterion = `${number}:${number}` | `${number}@${string}` | `$${number}`;
6
5
  export type SlicingCriteria = SingleSlicingCriterion[];
@@ -13,10 +12,10 @@ export declare class CriteriaParseError extends Error {
13
12
  /**
14
13
  * Takes a criterion in the form of `line:column` or `line@variable-name` and returns the corresponding node id
15
14
  */
16
- export declare function slicingCriterionToId<OtherInfo = NoInfo>(criterion: SingleSlicingCriterion, decorated: NormalizedAst<OtherInfo & ParentInformation>): NodeId;
15
+ export declare function slicingCriterionToId(criterion: SingleSlicingCriterion, idMap: AstIdMap): NodeId;
17
16
  export interface DecodedCriterion {
18
17
  criterion: SingleSlicingCriterion;
19
18
  id: NodeId;
20
19
  }
21
20
  export type DecodedCriteria = ReadonlyArray<DecodedCriterion>;
22
- export declare function convertAllSlicingCriteriaToIds(criteria: SlicingCriteria, decorated: NormalizedAst): DecodedCriteria;
21
+ export declare function convertAllSlicingCriteriaToIds(criteria: SlicingCriteria, decorated: AstIdMap): DecodedCriteria;
@@ -17,18 +17,18 @@ exports.CriteriaParseError = CriteriaParseError;
17
17
  /**
18
18
  * Takes a criterion in the form of `line:column` or `line@variable-name` and returns the corresponding node id
19
19
  */
20
- function slicingCriterionToId(criterion, decorated) {
20
+ function slicingCriterionToId(criterion, idMap) {
21
21
  let resolved;
22
22
  if (criterion.startsWith('$')) {
23
23
  resolved = (0, node_id_1.normalizeIdToNumberIfPossible)(criterion.substring(1));
24
24
  }
25
25
  else if (criterion.includes(':')) {
26
26
  const [line, column] = criterion.split(':').map(c => parseInt(c));
27
- resolved = locationToId([line, column], decorated.idMap);
27
+ resolved = locationToId([line, column], idMap);
28
28
  }
29
29
  else if (criterion.includes('@')) {
30
30
  const [line, name] = criterion.split(/@(.*)/s); // only split at first occurrence
31
- resolved = conventionalCriteriaToId(parseInt(line), name, decorated.idMap);
31
+ resolved = conventionalCriteriaToId(parseInt(line), name, idMap);
32
32
  }
33
33
  if (resolved === undefined) {
34
34
  throw new CriteriaParseError(`invalid slicing criterion ${criterion}`);