@eagleoutice/flowr 2.0.16 → 2.0.18

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 (71) 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 +36 -2
  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/cli/slicer-app.js +2 -2
  11. package/dataflow/environments/built-in.js +29 -9
  12. package/dataflow/environments/environment.js +4 -3
  13. package/dataflow/environments/resolve-by-name.d.ts +1 -1
  14. package/dataflow/environments/resolve-by-name.js +4 -4
  15. package/dataflow/graph/diff.js +1 -0
  16. package/dataflow/graph/graph.d.ts +24 -20
  17. package/dataflow/graph/graph.js +51 -24
  18. package/dataflow/graph/vertex.d.ts +6 -1
  19. package/dataflow/graph/vertex.js +21 -0
  20. package/dataflow/info.d.ts +1 -1
  21. package/dataflow/internal/process/functions/call/built-in/built-in-access.d.ts +2 -1
  22. package/dataflow/internal/process/functions/call/built-in/built-in-access.js +29 -7
  23. package/dataflow/internal/process/functions/call/built-in/built-in-apply.d.ts +14 -0
  24. package/dataflow/internal/process/functions/call/built-in/built-in-apply.js +65 -0
  25. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.d.ts +19 -2
  26. package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +40 -24
  27. package/dataflow/internal/process/functions/call/built-in/built-in-for-loop.js +1 -1
  28. package/dataflow/internal/process/functions/call/built-in/built-in-function-definition.js +10 -1
  29. package/dataflow/internal/process/functions/call/built-in/built-in-if-then-else.js +4 -4
  30. package/dataflow/internal/process/functions/call/built-in/built-in-library.js +7 -3
  31. package/dataflow/internal/process/functions/call/built-in/built-in-quote.d.ts +3 -2
  32. package/dataflow/internal/process/functions/call/built-in/built-in-quote.js +1 -1
  33. package/dataflow/internal/process/functions/call/built-in/built-in-replacement.d.ts +3 -2
  34. package/dataflow/internal/process/functions/call/built-in/built-in-replacement.js +2 -1
  35. package/dataflow/internal/process/functions/call/built-in/built-in-source.d.ts +2 -2
  36. package/dataflow/internal/process/functions/call/built-in/built-in-source.js +18 -7
  37. package/dataflow/internal/process/functions/call/built-in/{built-in-logical-bin-op.d.ts → built-in-special-bin-op.d.ts} +2 -1
  38. package/dataflow/internal/process/functions/call/built-in/{built-in-logical-bin-op.js → built-in-special-bin-op.js} +3 -3
  39. package/dataflow/internal/process/functions/call/common.d.ts +6 -2
  40. package/dataflow/internal/process/functions/call/common.js +36 -1
  41. package/dataflow/internal/process/functions/call/known-call-handling.d.ts +8 -3
  42. package/dataflow/internal/process/functions/call/known-call-handling.js +10 -7
  43. package/dataflow/internal/process/functions/call/named-call-handling.js +3 -1
  44. package/dataflow/internal/process/functions/call/unnamed-call-handling.js +1 -0
  45. package/dataflow/internal/process/functions/process-argument.js +0 -28
  46. package/package.json +3 -2
  47. package/r-bridge/data/data.d.ts +10 -0
  48. package/r-bridge/data/data.js +12 -0
  49. package/r-bridge/lang-4.x/ast/model/operators.js +1 -1
  50. package/reconstruct/auto-select/auto-select-defaults.d.ts +1 -6
  51. package/reconstruct/auto-select/auto-select-defaults.js +1 -13
  52. package/reconstruct/reconstruct.js +1 -1
  53. package/slicing/criterion/parse.d.ts +3 -4
  54. package/slicing/criterion/parse.js +3 -3
  55. package/slicing/static/static-slicer.d.ts +1 -1
  56. package/slicing/static/static-slicer.js +10 -4
  57. package/statistics/statistics.js +1 -1
  58. package/util/json.d.ts +1 -1
  59. package/util/json.js +3 -3
  60. package/util/logic.d.ts +5 -1
  61. package/util/version.js +1 -1
  62. package/abstract-interpretation/domain.d.ts +0 -57
  63. package/abstract-interpretation/domain.js +0 -176
  64. package/abstract-interpretation/handler/binop/binop.d.ts +0 -15
  65. package/abstract-interpretation/handler/binop/binop.js +0 -42
  66. package/abstract-interpretation/handler/binop/operators.d.ts +0 -2
  67. package/abstract-interpretation/handler/binop/operators.js +0 -28
  68. package/abstract-interpretation/handler/handler.d.ts +0 -6
  69. package/abstract-interpretation/handler/handler.js +0 -3
  70. package/abstract-interpretation/processor.d.ts +0 -11
  71. package/abstract-interpretation/processor.js +0 -84
@@ -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,20 +8,24 @@ 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;
14
15
  readonly data: DataflowProcessorInformation<OtherInfo & ParentInformation>;
16
+ /** should arguments be processed from right to left? This does not affect the order recorded in the call but of the environments */
15
17
  readonly reverseOrder?: boolean;
16
18
  /** which arguments are to be marked as {@link EdgeType#NonStandardEvaluation|non-standard-evaluation}? */
17
19
  readonly markAsNSE?: readonly number[];
20
+ /** allows passing a data processor in-between each argument */
18
21
  readonly patchData?: (data: DataflowProcessorInformation<OtherInfo & ParentInformation>, arg: number) => DataflowProcessorInformation<OtherInfo & ParentInformation>;
22
+ /** Does the call have a side effect that we do not know a lot about which may have further consequences? */
23
+ readonly hasUnknownSideEffect?: boolean;
19
24
  }
20
25
  export interface ProcessKnownFunctionCallResult {
21
26
  readonly information: DataflowInformation;
22
27
  readonly processedArguments: readonly (DataflowInformation | undefined)[];
23
28
  readonly fnRef: IdentifierReference;
24
29
  }
25
- 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;
30
+ export declare function markNonStandardEvaluationEdges(markAsNSE: readonly number[], callArgs: readonly (DataflowInformation | undefined)[], finalGraph: DataflowGraph, rootId: NodeId): void;
31
+ export declare function processKnownFunctionCall<OtherInfo>({ name, args, rootId, data, reverseOrder, markAsNSE, forceArgs, patchData, hasUnknownSideEffect }: ProcessKnownFunctionCallInput<OtherInfo>): ProcessKnownFunctionCallResult;
@@ -5,10 +5,8 @@ const processor_1 = require("../../../../processor");
5
5
  const common_1 = require("./common");
6
6
  const graph_1 = require("../../../../graph/graph");
7
7
  const logger_1 = require("../../../../logger");
8
+ const log_1 = require("../../../../../util/log");
8
9
  function markNonStandardEvaluationEdges(markAsNSE, callArgs, finalGraph, rootId) {
9
- if (markAsNSE === undefined) {
10
- return;
11
- }
12
10
  for (const nse of markAsNSE) {
13
11
  if (nse < callArgs.length) {
14
12
  const arg = callArgs[nse];
@@ -22,14 +20,16 @@ function markNonStandardEvaluationEdges(markAsNSE, callArgs, finalGraph, rootId)
22
20
  }
23
21
  }
24
22
  exports.markNonStandardEvaluationEdges = markNonStandardEvaluationEdges;
25
- function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = false, markAsNSE = undefined, patchData = d => d }) {
23
+ function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = false, markAsNSE = undefined, forceArgs, patchData = d => d, hasUnknownSideEffect }) {
26
24
  const functionName = (0, processor_1.processDataflowFor)(name, data);
27
25
  const finalGraph = new graph_1.DataflowGraph(data.completeAst.idMap);
28
26
  const functionCallName = name.content;
29
- logger_1.dataflowLogger.debug(`Using ${rootId} (name: ${functionCallName}) as root for the named function call`);
27
+ (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `Processing known function call ${functionCallName} with ${args.length} arguments`);
30
28
  const processArgs = reverseOrder ? [...args].reverse() : args;
31
- const { finalEnv, callArgs, remainingReadInArgs, processedArguments } = (0, common_1.processAllArguments)({ functionName, args: processArgs, data, finalGraph, functionRootId: rootId, patchData });
32
- markNonStandardEvaluationEdges(markAsNSE, processedArguments, finalGraph, rootId);
29
+ const { finalEnv, callArgs, remainingReadInArgs, processedArguments } = (0, common_1.processAllArguments)({ functionName, args: processArgs, data, finalGraph, functionRootId: rootId, patchData, forceArgs });
30
+ if (markAsNSE) {
31
+ markNonStandardEvaluationEdges(markAsNSE, processedArguments, finalGraph, rootId);
32
+ }
33
33
  finalGraph.addVertex({
34
34
  tag: "function-call" /* VertexType.FunctionCall */,
35
35
  id: rootId,
@@ -40,6 +40,9 @@ function processKnownFunctionCall({ name, args, rootId, data, reverseOrder = fal
40
40
  controlDependencies: data.controlDependencies,
41
41
  args: reverseOrder ? [...callArgs].reverse() : callArgs
42
42
  });
43
+ if (hasUnknownSideEffect) {
44
+ finalGraph.markIdForUnknownSideEffects(rootId);
45
+ }
43
46
  const inIds = remainingReadInArgs;
44
47
  const fnRef = { nodeId: rootId, name: functionCallName, controlDependencies: data.controlDependencies, call: true };
45
48
  inIds.push(fnRef);
@@ -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.18",
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";
@@ -226,6 +231,11 @@ export declare const flowrCapabilities: {
226
231
  readonly id: "local-equal-assignment";
227
232
  readonly supported: "fully";
228
233
  readonly description: "_Handle `x = 3`, `x$y := 3`, ..._";
234
+ }, {
235
+ readonly name: "Local Table Assignment";
236
+ readonly id: "local-table-assignment";
237
+ readonly supported: "fully";
238
+ readonly description: "_Handle `x[,a:=3,]`, ..._";
229
239
  }, {
230
240
  readonly name: "Super Left Assignment";
231
241
  readonly id: "super-left-assignment";
@@ -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',
@@ -282,6 +288,12 @@ exports.flowrCapabilities = {
282
288
  supported: 'fully',
283
289
  description: '_Handle `x = 3`, `x$y := 3`, ..._'
284
290
  },
291
+ {
292
+ name: 'Local Table Assignment',
293
+ id: 'local-table-assignment',
294
+ supported: 'fully',
295
+ description: '_Handle `x[,a:=3,]`, ..._'
296
+ },
285
297
  {
286
298
  name: 'Super Left Assignment',
287
299
  id: 'super-left-assignment',
@@ -34,7 +34,7 @@ exports.OperatorDatabase = {
34
34
  '%in%': { name: 'matching operator', stringUsedInRAst: '%in%', stringUsedInternally: '%in%', writtenAs: 'infix', arity: 2 /* OperatorArity.Binary */, usedAs: 'operation', capabilities: ['binary-operator', 'infix-calls', 'special-operator', 'function-calls'] },
35
35
  /* assignment */
36
36
  '<-': { name: 'left assignment', stringUsedInRAst: "LEFT_ASSIGN" /* RawRType.LeftAssign */, stringUsedInternally: '<-', writtenAs: 'infix', arity: 2 /* OperatorArity.Binary */, usedAs: 'assignment', capabilities: ['binary-operator', 'infix-calls', 'assignment-functions', 'local-left-assignment', 'function-calls'] },
37
- ':=': { name: 'left assignment', stringUsedInRAst: "LEFT_ASSIGN" /* RawRType.LeftAssign */, stringUsedInternally: ':=', writtenAs: 'infix', arity: 2 /* OperatorArity.Binary */, usedAs: 'assignment', capabilities: ['binary-operator', 'infix-calls', 'assignment-functions', 'local-equal-assignment', 'function-calls'] },
37
+ ':=': { name: 'left assignment', stringUsedInRAst: "LEFT_ASSIGN" /* RawRType.LeftAssign */, stringUsedInternally: ':=', writtenAs: 'infix', arity: 2 /* OperatorArity.Binary */, usedAs: 'assignment', capabilities: ['binary-operator', 'infix-calls', 'assignment-functions', 'local-table-assignment', 'function-calls'] },
38
38
  '<<-': { name: 'left global assignment', stringUsedInRAst: "LEFT_ASSIGN" /* RawRType.LeftAssign */, stringUsedInternally: '<<-', writtenAs: 'infix', arity: 2 /* OperatorArity.Binary */, usedAs: 'assignment', capabilities: ['binary-operator', 'infix-calls', 'assignment-functions', 'super-left-assignment', 'function-calls'] },
39
39
  '->': { name: 'right assignment', stringUsedInRAst: "RIGHT_ASSIGN" /* RawRType.RightAssign */, stringUsedInternally: '->', writtenAs: 'infix', arity: 2 /* OperatorArity.Binary */, usedAs: 'assignment', capabilities: ['binary-operator', 'infix-calls', 'assignment-functions', 'local-right-assignment', 'function-calls'] },
40
40
  '->>': { name: 'right global assignment', stringUsedInRAst: "RIGHT_ASSIGN" /* RawRType.RightAssign */, stringUsedInternally: '->>', writtenAs: 'infix', arity: 2 /* OperatorArity.Binary */, usedAs: 'assignment', capabilities: ['binary-operator', 'infix-calls', 'assignment-functions', 'super-right-assignment', 'function-calls'] },
@@ -1,4 +1,4 @@
1
- import type { NoInfo, RNode } from '../../r-bridge/lang-4.x/ast/model/model';
1
+ import type { RNode } from '../../r-bridge/lang-4.x/ast/model/model';
2
2
  import type { ParentInformation, NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
3
3
  /**
4
4
  * The structure of the predicate that should be used to determine
@@ -14,8 +14,3 @@ export type AutoSelectPredicate = (node: RNode<ParentInformation>, fullAst: Norm
14
14
  * A variant of the {@link AutoSelectPredicate} which does not select any additional statements (~&gt; false)
15
15
  */
16
16
  export declare function doNotAutoSelect(_node: RNode): boolean;
17
- /**
18
- * A variant of the {@link AutoSelectPredicate} which does its best
19
- * to select any kind of library import automatically.
20
- */
21
- export declare function autoSelectLibrary<Info = NoInfo>(node: RNode<Info>): boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.autoSelectLibrary = exports.doNotAutoSelect = void 0;
3
+ exports.doNotAutoSelect = void 0;
4
4
  /**
5
5
  * A variant of the {@link AutoSelectPredicate} which does not select any additional statements (~&gt; false)
6
6
  */
@@ -8,16 +8,4 @@ function doNotAutoSelect(_node) {
8
8
  return false;
9
9
  }
10
10
  exports.doNotAutoSelect = doNotAutoSelect;
11
- const libraryFunctionCall = /^(library|require|((require|load|attach)Namespace))$/;
12
- /**
13
- * A variant of the {@link AutoSelectPredicate} which does its best
14
- * to select any kind of library import automatically.
15
- */
16
- function autoSelectLibrary(node) {
17
- if (node.type !== "RFunctionCall" /* RType.FunctionCall */ || !node.named) {
18
- return false;
19
- }
20
- return libraryFunctionCall.test(node.functionName.content);
21
- }
22
- exports.autoSelectLibrary = autoSelectLibrary;
23
11
  //# sourceMappingURL=auto-select-defaults.js.map
@@ -424,7 +424,7 @@ function removeOuterExpressionListIfApplicable(result, linesWithAutoSelected) {
424
424
  *
425
425
  * @returns The number of lines for which `autoSelectIf` triggered, as well as the reconstructed code itself.
426
426
  */
427
- function reconstructToCode(ast, selection, autoSelectIf = auto_select_defaults_1.autoSelectLibrary) {
427
+ function reconstructToCode(ast, selection, autoSelectIf = auto_select_defaults_1.doNotAutoSelect) {
428
428
  if (exports.reconstructLogger.settings.minLevel <= 1 /* LogLevel.Trace */) {
429
429
  exports.reconstructLogger.trace(`reconstruct ast with ids: ${JSON.stringify([...selection])}`);
430
430
  }
@@ -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}`);
@@ -13,4 +13,4 @@ export declare const slicerLogger: import("tslog").Logger<import("tslog").ILogOb
13
13
  * @param criteria - The criteras to slice on.
14
14
  * @param threshold - The maximum number of nodes to visit in the graph. If the threshold is reached, the slice will side with inclusion and drop its minimal guarantee. The limit ensures that the algorithm halts.
15
15
  */
16
- export declare function staticSlicing(graph: DataflowGraph, ast: NormalizedAst, criteria: SlicingCriteria, threshold?: number): Readonly<SliceResult>;
16
+ export declare function staticSlicing(graph: DataflowGraph, { idMap }: NormalizedAst, criteria: SlicingCriteria, threshold?: number): Readonly<SliceResult>;
@@ -20,9 +20,9 @@ exports.slicerLogger = log_1.log.getSubLogger({ name: 'slicer' });
20
20
  * @param criteria - The criteras to slice on.
21
21
  * @param threshold - The maximum number of nodes to visit in the graph. If the threshold is reached, the slice will side with inclusion and drop its minimal guarantee. The limit ensures that the algorithm halts.
22
22
  */
23
- function staticSlicing(graph, ast, criteria, threshold = 75) {
23
+ function staticSlicing(graph, { idMap }, criteria, threshold = 75) {
24
24
  (0, assert_1.guard)(criteria.length > 0, 'must have at least one seed id to calculate slice');
25
- const decodedCriteria = (0, parse_1.convertAllSlicingCriteriaToIds)(criteria, ast);
25
+ const decodedCriteria = (0, parse_1.convertAllSlicingCriteriaToIds)(criteria, idMap);
26
26
  (0, log_1.expensiveTrace)(exports.slicerLogger, () => `calculating slice for ${decodedCriteria.length} seed criteria: ${decodedCriteria.map(s => JSON.stringify(s)).join(', ')}`);
27
27
  const queue = new visiting_queue_1.VisitingQueue(threshold);
28
28
  let minDepth = Number.MAX_SAFE_INTEGER;
@@ -34,9 +34,15 @@ function staticSlicing(graph, ast, criteria, threshold = 75) {
34
34
  for (const { id: startId } of decodedCriteria) {
35
35
  queue.add(startId, emptyEnv, basePrint, false);
36
36
  // retrieve the minimum depth of all nodes to only add control dependencies if they are "part" of the current execution
37
- minDepth = Math.min(minDepth, ast.idMap.get(startId)?.info.depth ?? minDepth);
37
+ minDepth = Math.min(minDepth, idMap.get(startId)?.info.depth ?? minDepth);
38
38
  sliceSeedIds.add(startId);
39
39
  }
40
+ /* additionally,
41
+ * include all the implicit side effects that we have to consider as we are unable to narrow them down
42
+ */
43
+ for (const id of graph.unknownSideEffects) {
44
+ queue.add(id, emptyEnv, basePrint, true);
45
+ }
40
46
  }
41
47
  while (queue.nonEmpty()) {
42
48
  const current = queue.next();
@@ -52,7 +58,7 @@ function staticSlicing(graph, ast, criteria, threshold = 75) {
52
58
  if (currentVertex.controlDependencies && currentVertex.controlDependencies.length > 0) {
53
59
  const topLevel = graph.isRoot(id) || sliceSeedIds.has(id);
54
60
  for (const cd of currentVertex.controlDependencies.filter(({ id }) => !queue.hasId(id))) {
55
- if (!topLevel || (ast.idMap.get(cd.id)?.info.depth ?? 0) <= minDepth) {
61
+ if (!topLevel || (idMap.get(cd.id)?.info.depth ?? 0) <= minDepth) {
56
62
  queue.add(cd.id, baseEnvironment, baseEnvFingerprint, false);
57
63
  }
58
64
  }
@@ -64,7 +64,7 @@ function initializeFeatureStatistics() {
64
64
  const result = {};
65
65
  for (const key of feature_1.allFeatureNames) {
66
66
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
67
- result[key] = JSON.parse(JSON.stringify(feature_1.ALL_FEATURES[key].initialValue, json_1.jsonReplacer), json_1.jsonRetriever);
67
+ result[key] = JSON.parse(JSON.stringify(feature_1.ALL_FEATURES[key].initialValue, json_1.jsonReplacer), json_1.jsonBigIntRetriever);
68
68
  }
69
69
  return result;
70
70
  }
package/util/json.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export declare function jsonReplacer(key: any, value: any): any;
2
- export declare function jsonRetriever(key: string, value: unknown): unknown;
2
+ export declare function jsonBigIntRetriever(key: string, value: unknown): unknown;