@eagleoutice/flowr 2.10.3 → 2.10.5

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 (94) hide show
  1. package/README.md +43 -26
  2. package/abstract-interpretation/absint-visitor.d.ts +17 -21
  3. package/abstract-interpretation/absint-visitor.js +47 -48
  4. package/abstract-interpretation/data-frame/dataframe-domain.d.ts +0 -3
  5. package/abstract-interpretation/data-frame/shape-inference.d.ts +2 -1
  6. package/abstract-interpretation/data-frame/shape-inference.js +5 -4
  7. package/abstract-interpretation/domains/abstract-domain.d.ts +17 -16
  8. package/abstract-interpretation/domains/abstract-domain.js +25 -27
  9. package/abstract-interpretation/domains/bounded-set-domain.js +1 -1
  10. package/abstract-interpretation/domains/multi-value-state-domain.d.ts +32 -0
  11. package/abstract-interpretation/domains/multi-value-state-domain.js +60 -0
  12. package/abstract-interpretation/domains/partial-product-domain.d.ts +43 -0
  13. package/abstract-interpretation/domains/partial-product-domain.js +163 -0
  14. package/abstract-interpretation/domains/product-domain.d.ts +2 -29
  15. package/abstract-interpretation/domains/product-domain.js +6 -123
  16. package/abstract-interpretation/domains/set-range-domain.js +3 -3
  17. package/abstract-interpretation/domains/set-upper-bound-domain.js +1 -1
  18. package/abstract-interpretation/domains/singleton-domain.js +1 -1
  19. package/abstract-interpretation/domains/state-abstract-domain.d.ts +13 -28
  20. package/abstract-interpretation/domains/state-abstract-domain.js +16 -38
  21. package/abstract-interpretation/domains/state-domain-like.d.ts +36 -0
  22. package/abstract-interpretation/domains/state-domain-like.js +3 -0
  23. package/cli/flowr.js +11 -1
  24. package/config.d.ts +7 -0
  25. package/config.js +22 -3
  26. package/control-flow/semantic-cfg-guided-visitor.d.ts +4 -0
  27. package/control-flow/semantic-cfg-guided-visitor.js +20 -32
  28. package/dataflow/environments/default-builtin-config.d.ts +10 -0
  29. package/dataflow/environments/default-builtin-config.js +2 -1
  30. package/dataflow/internal/process/functions/call/built-in/built-in-eval.d.ts +2 -0
  31. package/dataflow/internal/process/functions/call/built-in/built-in-eval.js +38 -21
  32. package/documentation/doc-readme.js +13 -2
  33. package/documentation/wiki-absint.d.ts +1 -2
  34. package/documentation/wiki-absint.js +34 -10
  35. package/documentation/wiki-analyzer.js +3 -4
  36. package/documentation/wiki-interface.js +21 -16
  37. package/documentation/wiki-linter.js +1 -1
  38. package/linter/linter-rules.d.ts +12 -12
  39. package/linter/linter-rules.js +2 -2
  40. package/linter/rules/network-functions.d.ts +1 -1
  41. package/linter/rules/network-functions.js +8 -2
  42. package/linter/rules/problematic-inputs.d.ts +43 -0
  43. package/linter/rules/problematic-inputs.js +110 -0
  44. package/linter/rules/seeded-randomness.d.ts +1 -1
  45. package/linter/rules/seeded-randomness.js +8 -1
  46. package/package.json +4 -4
  47. package/project/flowr-analyzer-builder.d.ts +6 -3
  48. package/project/flowr-analyzer-builder.js +12 -5
  49. package/project/plugins/file-plugins/files/flowr-rmarkdown-file.d.ts +4 -3
  50. package/project/plugins/file-plugins/files/flowr-rmarkdown-file.js +17 -4
  51. package/project/plugins/flowr-analyzer-plugin.d.ts +1 -1
  52. package/project/plugins/flowr-analyzer-plugin.js +1 -1
  53. package/queries/catalog/call-context-query/call-context-query-executor.js +2 -2
  54. package/queries/catalog/call-context-query/call-context-query-format.d.ts +1 -1
  55. package/queries/catalog/call-context-query/call-context-query-format.js +1 -2
  56. package/queries/catalog/dependencies-query/function-info/read-functions.js +6 -0
  57. package/queries/catalog/dependencies-query/function-info/write-functions.js +7 -0
  58. package/queries/catalog/input-sources-query/input-source-functions.d.ts +6 -0
  59. package/queries/catalog/input-sources-query/input-source-functions.js +50 -0
  60. package/queries/catalog/input-sources-query/input-sources-query-executor.d.ts +1 -1
  61. package/queries/catalog/input-sources-query/input-sources-query-executor.js +19 -31
  62. package/queries/catalog/input-sources-query/input-sources-query-format.d.ts +2 -1
  63. package/queries/catalog/input-sources-query/input-sources-query-format.js +26 -8
  64. package/queries/catalog/input-sources-query/simple-input-classifier.d.ts +33 -28
  65. package/queries/catalog/input-sources-query/simple-input-classifier.js +192 -99
  66. package/r-bridge/lang-4.x/ast/model/model.d.ts +4 -4
  67. package/r-bridge/lang-4.x/ast/model/nodes/r-access.d.ts +3 -3
  68. package/r-bridge/lang-4.x/ast/model/nodes/r-argument.d.ts +3 -3
  69. package/r-bridge/lang-4.x/ast/model/nodes/r-binary-op.d.ts +3 -3
  70. package/r-bridge/lang-4.x/ast/model/nodes/r-break.d.ts +3 -3
  71. package/r-bridge/lang-4.x/ast/model/nodes/r-comment.d.ts +3 -3
  72. package/r-bridge/lang-4.x/ast/model/nodes/r-expression-list.d.ts +3 -3
  73. package/r-bridge/lang-4.x/ast/model/nodes/r-for-loop.d.ts +3 -3
  74. package/r-bridge/lang-4.x/ast/model/nodes/r-function-call.d.ts +3 -3
  75. package/r-bridge/lang-4.x/ast/model/nodes/r-function-definition.d.ts +3 -3
  76. package/r-bridge/lang-4.x/ast/model/nodes/r-if-then-else.d.ts +3 -3
  77. package/r-bridge/lang-4.x/ast/model/nodes/r-line-directive.d.ts +3 -3
  78. package/r-bridge/lang-4.x/ast/model/nodes/r-logical.d.ts +3 -3
  79. package/r-bridge/lang-4.x/ast/model/nodes/r-next.d.ts +3 -3
  80. package/r-bridge/lang-4.x/ast/model/nodes/r-number.d.ts +3 -3
  81. package/r-bridge/lang-4.x/ast/model/nodes/r-parameter.d.ts +3 -3
  82. package/r-bridge/lang-4.x/ast/model/nodes/r-pipe.d.ts +3 -3
  83. package/r-bridge/lang-4.x/ast/model/nodes/r-repeat-loop.d.ts +3 -3
  84. package/r-bridge/lang-4.x/ast/model/nodes/r-string.d.ts +3 -3
  85. package/r-bridge/lang-4.x/ast/model/nodes/r-symbol.d.ts +3 -3
  86. package/r-bridge/lang-4.x/ast/model/nodes/r-unary-op.d.ts +3 -3
  87. package/r-bridge/lang-4.x/ast/model/nodes/r-while-loop.d.ts +3 -3
  88. package/util/record.d.ts +18 -3
  89. package/util/record.js +22 -1
  90. package/util/version.js +1 -1
  91. package/linter/rules/problematic-eval.d.ts +0 -44
  92. package/linter/rules/problematic-eval.js +0 -83
  93. package/project/plugins/flowr-analyzer-plugin-defaults.d.ts +0 -5
  94. package/project/plugins/flowr-analyzer-plugin-defaults.js +0 -37
@@ -2,19 +2,20 @@ import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/nod
2
2
  import type { DataflowGraph } from '../../../dataflow/graph/graph';
3
3
  import type { MergeableRecord } from '../../../util/objects';
4
4
  import { Identifier } from '../../../dataflow/environments/identifier';
5
+ import type { FlowrSearchLike } from '../../../search/flowr-search-builder';
5
6
  /**
6
7
  * Lattice flattening until we have a taint engine :)
7
8
  * Please note that the classifier considers this basis with a set-lift,
8
9
  * joining differing lattice elements.
9
10
  *
10
11
  *```
11
- * [ Unknown ]
12
- * / / | \ \
13
- *[Param] [File] [Net] [Rand] [Scope]
14
- * \ \ | / /
15
- * [ DerivedConstant ]
16
- * |
17
- * [ Constant ]
12
+ * [ Unknown ]
13
+ * |
14
+ * [Param] [File] [Net], ...
15
+ * |
16
+ * [ DerivedConstant ]
17
+ * |
18
+ * [ Constant ]
18
19
  *```
19
20
  *
20
21
  */
@@ -23,6 +24,14 @@ export declare enum InputType {
23
24
  File = "file",
24
25
  Network = "net",
25
26
  Random = "rand",
27
+ /** Calls to system/system2 and similar */
28
+ System = "system",
29
+ /** Calls to .C / Fortran interfaces (foreign function interfaces) */
30
+ Ffi = "ffi",
31
+ /** Language objects (quote/substitute/etc.) */
32
+ Lang = "lang",
33
+ /** Global options / option accessors (options, getOption) */
34
+ Options = "options",
26
35
  Constant = "const",
27
36
  /** Read from environment/call scope */
28
37
  Scope = "scope",
@@ -40,16 +49,24 @@ export declare enum InputTraceType {
40
49
  /** Not fully known origin */
41
50
  Unknown = "unknown"
42
51
  }
52
+ /**
53
+ * Scalar R constant values representable in TypeScript.
54
+ * `null` corresponds to R's `NULL`.
55
+ * NA values are not included (they have no direct TS equivalent).
56
+ */
57
+ export type ConstantValue = string | number | boolean | null;
43
58
  /**
44
59
  * Object attached to an input source
45
60
  * @see {@link InputSources}
46
61
  */
47
62
  export interface InputSource extends MergeableRecord {
48
63
  id: NodeId;
49
- type: InputType[];
64
+ types: InputType[];
50
65
  trace: InputTraceType;
51
66
  /** if the trace is affected by control dependencies, they are classified too, this is a duplicate free array */
52
67
  cds?: InputType[];
68
+ /** the concrete scalar value when the source is a constant or a pure alias of one */
69
+ value?: ConstantValue;
53
70
  }
54
71
  /**
55
72
  * Map of input sources, keyed by the node id of the input source. Each input source is classified with an {@link InputSource} object.
@@ -59,34 +76,22 @@ export type InputSources = InputSource[];
59
76
  * This is either an {@link NodeId|id} of a known functions all of that category (e.g., you can issue a dependencies query before and then pass all
60
77
  * identified ids to this query here).
61
78
  */
62
- export type InputClassifierFunctionIdentifier = Identifier | NodeId;
79
+ export type InputClassifierFunctionIdentifiers = readonly (Identifier | NodeId)[];
63
80
  /**
64
- * For the specifications of `pureFns` etc. please have a look at {@link InputClassifierFunctionIdentifier}.
81
+ * For the specifications of `pure` etc. please have a look at {@link InputClassifierFunctionIdentifiers}.
65
82
  */
66
- export interface InputClassifierConfig extends MergeableRecord {
83
+ export interface InputClassifierConfig<Functions extends InputClassifierFunctionIdentifiers | FlowrSearchLike = readonly Identifier[] | FlowrSearchLike> extends Partial<Record<InputType, Functions>> {
67
84
  /**
68
85
  * Functions which are considered to be pure (i.e., deterministic, trusted, safe, idempotent on the lub of the input types)
69
86
  */
70
- pureFns: readonly InputClassifierFunctionIdentifier[];
71
- /**
72
- * Functions that read from the network
73
- */
74
- networkFns: readonly InputClassifierFunctionIdentifier[];
75
- /**
76
- * Functions that produce a random value
77
- * Note: may need to check with respect to seeded randomness
78
- */
79
- randomFns: readonly InputClassifierFunctionIdentifier[];
80
- /**
81
- * Functions that read from the file system
82
- */
83
- readFileFns: readonly InputClassifierFunctionIdentifier[];
87
+ [InputTraceType.Pure]?: Functions;
84
88
  }
85
89
  /**
86
90
  * Takes the given id which is expected to either be:
87
91
  * - a function call - in this case all arguments are considered to be inputs (additionally to all read edges from the function call in the dataflow graph)
88
- * - anything else - in that case the node itself is considered as an "input" - please note that in these scenarios the *return* value will only contain one mapping - that for the id you pased in.
92
+ * - anything else - in that case the node itself is considered as an "input" - please note that in these scenarios the *return* value will only contain one mapping - that for the id you passed in.
89
93
  *
90
- * This method traces the dependencies in the dataflow graph using the specification of functions passed in
94
+ * This method traces the dependencies in the dataflow graph using the specification of functions passed in.
95
+ * For the scope escape analysis, pass on the full, non-reduced DFG as `fullDfg`.
91
96
  */
92
- export declare function classifyInput(id: NodeId, dfg: DataflowGraph, config: InputClassifierConfig): InputSources;
97
+ export declare function classifyInput(id: NodeId, dfg: DataflowGraph, config: InputClassifierConfig<InputClassifierFunctionIdentifiers>, fullDfg?: DataflowGraph): InputSources;
@@ -6,17 +6,110 @@ const graph_1 = require("../../../dataflow/graph/graph");
6
6
  const objects_1 = require("../../../util/objects");
7
7
  const vertex_1 = require("../../../dataflow/graph/vertex");
8
8
  const df_helper_1 = require("../../../dataflow/graph/df-helper");
9
+ const edge_1 = require("../../../dataflow/graph/edge");
9
10
  const identifier_1 = require("../../../dataflow/environments/identifier");
10
11
  const assert_1 = require("../../../util/assert");
11
12
  const arrays_1 = require("../../../util/collections/arrays");
12
13
  const built_in_proc_name_1 = require("../../../dataflow/environments/built-in-proc-name");
14
+ const record_1 = require("../../../util/record");
15
+ const r_number_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-number");
16
+ const r_string_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-string");
17
+ const r_logical_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-logical");
18
+ const r_symbol_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol");
19
+ const convert_values_1 = require("../../../r-bridge/lang-4.x/convert-values");
20
+ function isConstantLike(type) {
21
+ return type === InputType.Constant || type === InputType.DerivedConstant;
22
+ }
23
+ /** Returns the common value shared by all defined entries, or `undefined` if they disagree or all are `undefined`. */
24
+ function singleValue(values) {
25
+ let result;
26
+ let seen = false;
27
+ for (const v of values) {
28
+ if (v === undefined) {
29
+ return undefined;
30
+ }
31
+ if (!seen) {
32
+ result = v;
33
+ seen = true;
34
+ }
35
+ else if (v !== result) {
36
+ return undefined;
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ /**
42
+ * Accumulates types, control-dependency types, values, and purity while traversing origin
43
+ * chains. Call {@link build} to produce the resulting {@link InputSource}.
44
+ */
45
+ class ClassificationAccumulator {
46
+ types = [];
47
+ cds = [];
48
+ values = [];
49
+ allPure = true;
50
+ merge(c) {
51
+ this.types.push(...c.types);
52
+ this.values.push(c.value);
53
+ if (c.cds) {
54
+ this.cds.push(...c.cds);
55
+ }
56
+ if (c.trace !== InputTraceType.Pure) {
57
+ this.allPure = false;
58
+ }
59
+ }
60
+ pushUnknown() {
61
+ this.types.push(InputType.Unknown);
62
+ this.values.push(undefined);
63
+ }
64
+ build(id) {
65
+ const types = this.types.length === 0 ? [InputType.Unknown] : (0, arrays_1.uniqueArray)(this.types);
66
+ const trace = this.allPure ? InputTraceType.Pure : InputTraceType.Alias;
67
+ const src = { id, types, trace };
68
+ const cds = this.cds.length === 0 ? undefined : (0, arrays_1.uniqueArray)(this.cds);
69
+ if (cds) {
70
+ src.cds = cds;
71
+ }
72
+ if (types.every(isConstantLike)) {
73
+ const v = singleValue(this.values);
74
+ if (v !== undefined) {
75
+ src.value = v;
76
+ }
77
+ }
78
+ return src;
79
+ }
80
+ }
13
81
  class InputClassifier {
14
82
  dfg;
15
83
  config;
16
84
  cache = new Map();
17
- constructor(dfg, config) {
85
+ fullDfg;
86
+ constructor(dfg, config, fullDfg) {
18
87
  this.dfg = dfg;
19
88
  this.config = config;
89
+ this.fullDfg = fullDfg;
90
+ }
91
+ isDefinedByOnCall(id) {
92
+ const out = (this.fullDfg ?? this.dfg).outgoingEdges(id) ?? new Map();
93
+ return out.values().some(e => edge_1.DfEdge.includesType(e, edge_1.EdgeType.DefinedByOnCall));
94
+ }
95
+ extractConstantValue(id) {
96
+ const node = this.dfg.idMap?.get(id);
97
+ if (node === undefined) {
98
+ return undefined;
99
+ }
100
+ if (r_number_1.RNumber.is(node)) {
101
+ return node.content.num;
102
+ }
103
+ if (r_string_1.RString.is(node)) {
104
+ return node.content.str;
105
+ }
106
+ if (r_logical_1.RLogical.is(node)) {
107
+ return node.content;
108
+ }
109
+ if (r_symbol_1.RSymbol.is(node) && node.content === convert_values_1.RNull) {
110
+ return null;
111
+ }
112
+ return undefined;
20
113
  }
21
114
  classifyEntry(vertex) {
22
115
  const cached = this.cache.get(vertex.id);
@@ -24,10 +117,16 @@ class InputClassifier {
24
117
  return cached;
25
118
  }
26
119
  // insert temporary unknown to break cycles
27
- this.cache.set(vertex.id, { id: vertex.id, type: [InputType.Unknown], trace: InputTraceType.Unknown });
120
+ this.cache.set(vertex.id, { id: vertex.id, types: [InputType.Unknown], trace: InputTraceType.Unknown });
28
121
  switch (vertex.tag) {
29
- case vertex_1.VertexType.Value:
30
- return this.classifyCdsAndReturn(vertex, { id: vertex.id, type: [InputType.Constant], trace: InputTraceType.Unknown });
122
+ case vertex_1.VertexType.Value: {
123
+ const src = { id: vertex.id, types: [InputType.Constant], trace: InputTraceType.Unknown };
124
+ const v = this.extractConstantValue(vertex.id);
125
+ if (v !== undefined) {
126
+ src.value = v;
127
+ }
128
+ return this.classifyCdsAndReturn(vertex, src);
129
+ }
31
130
  case vertex_1.VertexType.FunctionCall:
32
131
  return this.classifyFunctionCall(vertex);
33
132
  case vertex_1.VertexType.VariableDefinition:
@@ -35,7 +134,7 @@ class InputClassifier {
35
134
  case vertex_1.VertexType.Use:
36
135
  return this.classifyVariable(vertex);
37
136
  default:
38
- return this.classifyCdsAndReturn(vertex, { id: vertex.id, type: [InputType.Unknown], trace: InputTraceType.Unknown });
137
+ return this.classifyCdsAndReturn(vertex, { id: vertex.id, types: [InputType.Unknown], trace: InputTraceType.Unknown });
39
138
  }
40
139
  }
41
140
  classifyFunctionCall(call) {
@@ -57,20 +156,18 @@ class InputClassifier {
57
156
  }
58
157
  }
59
158
  }
60
- if (!matchesList(call, this.config.pureFns)) {
61
- if (matchesList(call, this.config.readFileFns)) {
62
- return this.classifyCdsAndReturn(call, { id: call.id, type: [InputType.File], trace: InputTraceType.Unknown });
63
- }
64
- else if (matchesList(call, this.config.networkFns)) {
65
- return this.classifyCdsAndReturn(call, { id: call.id, type: [InputType.Network], trace: InputTraceType.Unknown });
66
- }
67
- else if (matchesList(call, this.config.randomFns)) {
68
- return this.classifyCdsAndReturn(call, { id: call.id, type: [InputType.Random], trace: InputTraceType.Unknown });
159
+ if (!matchesList(call, this.config.pure)) {
160
+ const types = [];
161
+ for (const [type, entry] of record_1.Record.entries(this.config)) {
162
+ if (record_1.Record.values(InputType).includes(type) && matchesList(call, entry)) {
163
+ types.push(type);
164
+ }
69
165
  }
70
- else {
166
+ if (types.length === 0) {
71
167
  // if it is not pure, we cannot classify based on the inputs, in that case we do not know!
72
- return this.classifyCdsAndReturn(call, { id: call.id, type: [InputType.Unknown], trace: InputTraceType.Unknown });
168
+ types.push(InputType.Unknown);
73
169
  }
170
+ return this.classifyCdsAndReturn(call, { id: call.id, types, trace: InputTraceType.Unknown });
74
171
  }
75
172
  // Otherwise, classify by arguments; pure functions get Known/Pure handling
76
173
  const argTypes = [];
@@ -91,111 +188,98 @@ class InputClassifier {
91
188
  }
92
189
  const classified = this.classifyEntry(argVtx);
93
190
  // collect all observed types from this argument
94
- argTypes.push(...classified.type);
191
+ argTypes.push(...classified.types);
95
192
  if (classified.cds) {
96
193
  cdTypes.push(...classified.cds);
97
194
  }
98
195
  }
99
196
  const cds = cdTypes.length > 0 ? (0, arrays_1.uniqueArray)(cdTypes) : undefined;
100
197
  // all arguments only contain constant-like types -> derived constant
101
- const allConstLike = argTypes.length > 0 && argTypes.every(t => t === InputType.Constant || t === InputType.DerivedConstant);
198
+ const allConstLike = argTypes.length > 0 && argTypes.every(isConstantLike);
102
199
  if (allConstLike) {
103
- return this.classifyCdsAndReturn(call, (0, objects_1.compactRecord)({ id: call.id, type: [InputType.DerivedConstant], trace: InputTraceType.Pure, cds }));
200
+ return this.classifyCdsAndReturn(call, (0, objects_1.compactRecord)({ id: call.id, types: [InputType.DerivedConstant], trace: InputTraceType.Pure, cds }));
104
201
  }
105
202
  argTypes.push(InputType.DerivedConstant);
106
- return this.classifyCdsAndReturn(call, (0, objects_1.compactRecord)({ id: call.id, type: (0, arrays_1.uniqueArray)(argTypes), trace: InputTraceType.Known, cds }));
203
+ return this.classifyCdsAndReturn(call, (0, objects_1.compactRecord)({ id: call.id, types: (0, arrays_1.uniqueArray)(argTypes), trace: InputTraceType.Known, cds }));
107
204
  }
108
205
  classifyVariable(vtx) {
109
206
  const origins = df_helper_1.Dataflow.origin(this.dfg, vtx.id);
110
207
  if (origins === undefined) {
111
- return this.classifyCdsAndReturn(vtx, { id: vtx.id, type: [InputType.Unknown], trace: InputTraceType.Unknown });
208
+ return this.classifyCdsAndReturn(vtx, { id: vtx.id, types: this.isDefinedByOnCall(vtx.id) ? [InputType.Scope] : [InputType.Unknown], trace: InputTraceType.Unknown });
112
209
  }
113
- const types = [];
114
- const cds = [];
115
- let allPure = true;
210
+ const acc = new ClassificationAccumulator();
116
211
  for (const o of origins) {
117
212
  if (o.type === 4 /* OriginType.ConstantOrigin */) {
118
- types.push(InputType.DerivedConstant);
119
- continue;
213
+ acc.types.push(InputType.DerivedConstant);
214
+ acc.values.push(this.extractConstantValue(o.id));
120
215
  }
121
- if (o.type === 0 /* OriginType.ReadVariableOrigin */ || o.type === 1 /* OriginType.WriteVariableOrigin */) {
122
- const v = this.dfg.getVertex(o.id);
123
- if (v) {
124
- // if this is a variable definition that is a parameter, classify as Parameter
125
- if (v.tag === vertex_1.VertexType.VariableDefinition && this.dfg.idMap?.get(v.id)?.info.role === "param-n" /* RoleInParent.ParameterName */) {
126
- types.push(InputType.Parameter);
127
- continue;
128
- }
129
- const c = this.classifyEntry(v);
130
- types.push(...c.type);
131
- if (c.cds) {
132
- cds.push(...c.cds);
133
- }
134
- if (c.trace !== InputTraceType.Pure) {
135
- allPure = false;
136
- }
137
- }
138
- else {
139
- types.push(InputType.Unknown);
140
- }
141
- continue;
216
+ else if (o.type === 0 /* OriginType.ReadVariableOrigin */ || o.type === 1 /* OriginType.WriteVariableOrigin */) {
217
+ this.classifyVariableOrigin(o.id, acc);
142
218
  }
143
- if (o.type === 2 /* OriginType.FunctionCallOrigin */ || o.type === 3 /* OriginType.BuiltInFunctionOrigin */) {
144
- const v = this.dfg.getVertex(o.id);
145
- if (v) {
146
- const c = this.classifyEntry(v);
147
- types.push(...c.type);
148
- if (c.cds) {
149
- cds.push(...c.cds);
150
- }
151
- if (c.trace !== InputTraceType.Pure) {
152
- allPure = false;
153
- }
154
- }
155
- else {
156
- types.push(InputType.Unknown);
157
- }
158
- continue;
219
+ else if (o.type === 2 /* OriginType.FunctionCallOrigin */ || o.type === 3 /* OriginType.BuiltInFunctionOrigin */) {
220
+ this.classifyByVertex(o.id, acc);
159
221
  }
160
- // unknown origin type
161
- types.push(InputType.Unknown);
222
+ else {
223
+ acc.pushUnknown();
224
+ }
225
+ }
226
+ return this.classifyCdsAndReturn(vtx, acc.build(vtx.id));
227
+ }
228
+ /**
229
+ * Resolves a variable definition or use origin, handling the special cases of
230
+ * scope-escaped variables (DefinedByOnCall) and parameter definitions.
231
+ */
232
+ classifyVariableOrigin(definitionId, acc) {
233
+ const v = this.dfg.getVertex(definitionId);
234
+ if (!v) {
235
+ acc.pushUnknown();
236
+ return;
237
+ }
238
+ // if the referenced definition is linked via defined-by-on-call to another
239
+ // id (e.g., a parameter linked to a caller argument), mark it as a Scope origin
240
+ if (this.isDefinedByOnCall(v.id)) {
241
+ acc.types.push(InputType.Scope);
242
+ acc.values.push(undefined);
243
+ acc.allPure = false;
244
+ }
245
+ // if this is a variable definition that is a parameter, classify as Parameter
246
+ if (v.tag === vertex_1.VertexType.VariableDefinition && this.dfg.idMap?.get(v.id)?.info.role === "param-n" /* RoleInParent.ParameterName */) {
247
+ acc.types.push(InputType.Parameter);
248
+ acc.values.push(undefined);
249
+ return;
250
+ }
251
+ acc.merge(this.classifyEntry(v));
252
+ }
253
+ classifyByVertex(id, acc) {
254
+ const v = this.dfg.getVertex(id);
255
+ if (v) {
256
+ acc.merge(this.classifyEntry(v));
257
+ }
258
+ else {
259
+ acc.pushUnknown();
162
260
  }
163
- const t = types.length === 0 ? [InputType.Unknown] : (0, arrays_1.uniqueArray)(types);
164
- const trace = allPure ? InputTraceType.Pure : InputTraceType.Alias;
165
- return this.classifyCdsAndReturn(vtx, { id: vtx.id, type: t, trace, cds: cds.length === 0 ? undefined : (0, arrays_1.uniqueArray)(cds) });
166
261
  }
167
262
  classifyVariableDefinition(vtx) {
168
263
  // parameter definitions are classified as Parameter
169
264
  if (this.dfg.idMap?.get(vtx.id)?.info.role === "param-n" /* RoleInParent.ParameterName */) {
170
- return this.classifyCdsAndReturn(vtx, { id: vtx.id, type: [InputType.Parameter], trace: InputTraceType.Unknown });
265
+ return this.classifyCdsAndReturn(vtx, { id: vtx.id, types: [InputType.Parameter], trace: InputTraceType.Unknown });
171
266
  }
172
267
  const sources = vtx.source;
173
268
  if (sources === undefined || sources.length === 0) {
174
269
  // fallback to unknown if we cannot find the value
175
- return this.classifyCdsAndReturn(vtx, { id: vtx.id, type: [InputType.Unknown], trace: InputTraceType.Unknown });
270
+ return this.classifyCdsAndReturn(vtx, { id: vtx.id, types: [InputType.Unknown], trace: InputTraceType.Unknown });
176
271
  }
177
- const types = [];
178
- const cds = [];
179
- let allPure = true;
272
+ const acc = new ClassificationAccumulator();
180
273
  for (const tid of sources) {
181
274
  const tv = this.dfg.getVertex(tid);
182
275
  if (tv) {
183
- const c = this.classifyEntry(tv);
184
- types.push(...c.type);
185
- if (c.cds) {
186
- cds.push(...c.cds);
187
- }
188
- if (c.trace !== InputTraceType.Pure) {
189
- allPure = false;
190
- }
276
+ acc.merge(this.classifyEntry(tv));
191
277
  }
192
278
  else {
193
- types.push(InputType.Unknown);
279
+ acc.pushUnknown();
194
280
  }
195
281
  }
196
- const t = types.length === 0 ? [InputType.Unknown] : (0, arrays_1.uniqueArray)(types);
197
- const trace = allPure ? InputTraceType.Pure : InputTraceType.Alias;
198
- return this.classifyCdsAndReturn(vtx, { id: vtx.id, type: t, trace, cds: cds.length === 0 ? undefined : (0, arrays_1.uniqueArray)(cds) });
282
+ return this.classifyCdsAndReturn(vtx, acc.build(vtx.id));
199
283
  }
200
284
  classifyCdsAndReturn(vtx, src) {
201
285
  if (vtx.cds) {
@@ -205,7 +289,7 @@ class InputClassifier {
205
289
  return undefined;
206
290
  }
207
291
  const e = this.classifyEntry(cv);
208
- return e.cds ? [...e.type, ...e.cds] : [...e.type];
292
+ return e.cds ? [...e.types, ...e.cds] : [...e.types];
209
293
  }).filter(assert_1.isNotUndefined).concat(src.cds ?? []));
210
294
  if (cds.length > 0) {
211
295
  src.cds = cds;
@@ -224,13 +308,13 @@ class InputClassifier {
224
308
  * joining differing lattice elements.
225
309
  *
226
310
  *```
227
- * [ Unknown ]
228
- * / / | \ \
229
- *[Param] [File] [Net] [Rand] [Scope]
230
- * \ \ | / /
231
- * [ DerivedConstant ]
232
- * |
233
- * [ Constant ]
311
+ * [ Unknown ]
312
+ * |
313
+ * [Param] [File] [Net], ...
314
+ * |
315
+ * [ DerivedConstant ]
316
+ * |
317
+ * [ Constant ]
234
318
  *```
235
319
  *
236
320
  */
@@ -240,6 +324,14 @@ var InputType;
240
324
  InputType["File"] = "file";
241
325
  InputType["Network"] = "net";
242
326
  InputType["Random"] = "rand";
327
+ /** Calls to system/system2 and similar */
328
+ InputType["System"] = "system";
329
+ /** Calls to .C / Fortran interfaces (foreign function interfaces) */
330
+ InputType["Ffi"] = "ffi";
331
+ /** Language objects (quote/substitute/etc.) */
332
+ InputType["Lang"] = "lang";
333
+ /** Global options / option accessors (options, getOption) */
334
+ InputType["Options"] = "options";
243
335
  InputType["Constant"] = "const";
244
336
  /** Read from environment/call scope */
245
337
  InputType["Scope"] = "scope";
@@ -259,11 +351,11 @@ var InputTraceType;
259
351
  InputTraceType["Unknown"] = "unknown";
260
352
  })(InputTraceType || (exports.InputTraceType = InputTraceType = {}));
261
353
  function matchesList(fn, list) {
262
- if (!list || list.length === 0) {
354
+ if (list === undefined || list.length === 0) {
263
355
  return false;
264
356
  }
265
357
  for (const id of list) {
266
- if (fn.id === id || identifier_1.Identifier.matches(id, fn.name)) {
358
+ if (fn.id === id || (identifier_1.Identifier.is(id) && identifier_1.Identifier.matches(id, fn.name))) {
267
359
  return true;
268
360
  }
269
361
  }
@@ -272,16 +364,17 @@ function matchesList(fn, list) {
272
364
  /**
273
365
  * Takes the given id which is expected to either be:
274
366
  * - a function call - in this case all arguments are considered to be inputs (additionally to all read edges from the function call in the dataflow graph)
275
- * - anything else - in that case the node itself is considered as an "input" - please note that in these scenarios the *return* value will only contain one mapping - that for the id you pased in.
367
+ * - anything else - in that case the node itself is considered as an "input" - please note that in these scenarios the *return* value will only contain one mapping - that for the id you passed in.
276
368
  *
277
- * This method traces the dependencies in the dataflow graph using the specification of functions passed in
369
+ * This method traces the dependencies in the dataflow graph using the specification of functions passed in.
370
+ * For the scope escape analysis, pass on the full, non-reduced DFG as `fullDfg`.
278
371
  */
279
- function classifyInput(id, dfg, config) {
372
+ function classifyInput(id, dfg, config, fullDfg) {
280
373
  const vtx = dfg.getVertex(id);
281
374
  if (!vtx) {
282
375
  return [];
283
376
  }
284
- const c = new InputClassifier(dfg, config);
377
+ const c = new InputClassifier(dfg, config, fullDfg);
285
378
  if (vtx.tag === vertex_1.VertexType.FunctionCall) {
286
379
  const ret = [];
287
380
  const args = vtx.args;
@@ -24,7 +24,7 @@ import type { RUnaryOp } from './nodes/r-unary-op';
24
24
  import type { RBinaryOp } from './nodes/r-binary-op';
25
25
  import type { RPipe } from './nodes/r-pipe';
26
26
  import type { RDelimiter } from './nodes/info/r-delimiter';
27
- import type { ParentInformation } from './processing/decorate';
27
+ import type { AstIdMap, ParentInformation } from './processing/decorate';
28
28
  import type { NodeId } from './processing/node-id';
29
29
  import type { OnEnter, OnExit } from './processing/visitor';
30
30
  import type { SingleOrArrayOrNothing } from '../../../../abstract-interpretation/normalized-ast-fold';
@@ -264,16 +264,16 @@ export declare const RNode: {
264
264
  * linear chain of parents leading to the root node.
265
265
  * @see {@link iterateParents} - to get all parents of a node
266
266
  */
267
- readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation>, idMap: Map<NodeId, RNode<OtherInfo & ParentInformation>>) => RNode<OtherInfo & ParentInformation> | undefined;
267
+ readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation>, idMap: AstIdMap<OtherInfo & ParentInformation>) => RNode<OtherInfo & ParentInformation> | undefined;
268
268
  /**
269
269
  * Returns an iterable of all parents of a node, starting with the direct parent and ending with the root node.
270
270
  */
271
- readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation> | undefined, idMap: Map<NodeId, RNode<OtherInfo & ParentInformation>>) => Generator<RNode<OtherInfo & ParentInformation>>;
271
+ readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation> | undefined, idMap: AstIdMap<OtherInfo & ParentInformation>) => Generator<RNode<OtherInfo & ParentInformation>>;
272
272
  /**
273
273
  * In contrast to the nesting stored in the {@link RNode} structure,
274
274
  * this function calculates the depth of a node by counting the number of parents until the root node is reached.
275
275
  */
276
- readonly depth: (this: void, node: RNode<ParentInformation>, idMap: Map<NodeId, RNode<ParentInformation>>) => number;
276
+ readonly depth: (this: void, node: RNode<ParentInformation>, idMap: AstIdMap<ParentInformation>) => number;
277
277
  /**
278
278
  * Collects all node ids within a tree given by a respective root node, but stops collecting at nodes where the given `stop` function returns `true`.
279
279
  * <p>
@@ -49,9 +49,9 @@ export declare const RAccess: {
49
49
  readonly visitAst: <OtherInfo = object>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo>>, onVisit?: import("../processing/visitor").OnEnter<OtherInfo>, onExit?: import("../processing/visitor").OnExit<OtherInfo>) => void;
50
50
  readonly collectAllIds: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => Set<import("../processing/node-id").NodeId>;
51
51
  readonly directChildren: <OtherInfo>(this: void, node: RNode<OtherInfo>) => readonly (RNode<OtherInfo> | typeof EmptyArgument)[];
52
- readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>, idMap: Map<import("../processing/node-id").NodeId, RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined;
53
- readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined, idMap: Map<import("../processing/node-id").NodeId, RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => Generator<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>;
54
- readonly depth: (this: void, node: RNode<import("../processing/decorate").ParentInformation>, idMap: Map<import("../processing/node-id").NodeId, RNode<import("../processing/decorate").ParentInformation>>) => number;
52
+ readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>, idMap: import("../processing/decorate").AstIdMap<OtherInfo & import("../processing/decorate").ParentInformation>) => RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined;
53
+ readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined, idMap: import("../processing/decorate").AstIdMap<OtherInfo & import("../processing/decorate").ParentInformation>) => Generator<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>;
54
+ readonly depth: (this: void, node: RNode<import("../processing/decorate").ParentInformation>, idMap: import("../processing/decorate").AstIdMap<import("../processing/decorate").ParentInformation>) => number;
55
55
  readonly collectAllIdsWithStop: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>, stop: (node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>) => boolean) => Set<import("../processing/node-id").NodeId>;
56
56
  readonly lexeme: <R extends RNode<import("../processing/decorate").ParentInformation>>(this: void, node: R | undefined) => R extends {
57
57
  lexeme: string;
@@ -68,9 +68,9 @@ export declare const RArgument: {
68
68
  readonly visitAst: <OtherInfo = object>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo>>, onVisit?: import("../processing/visitor").OnEnter<OtherInfo>, onExit?: import("../processing/visitor").OnExit<OtherInfo>) => void;
69
69
  readonly collectAllIds: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & ParentInformation>>) => Set<NodeId>;
70
70
  readonly directChildren: <OtherInfo>(this: void, node: RNode<OtherInfo>) => readonly (RNode<OtherInfo> | typeof EmptyArgument)[];
71
- readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation>, idMap: Map<NodeId, RNode<OtherInfo & ParentInformation>>) => RNode<OtherInfo & ParentInformation> | undefined;
72
- readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation> | undefined, idMap: Map<NodeId, RNode<OtherInfo & ParentInformation>>) => Generator<RNode<OtherInfo & ParentInformation>>;
73
- readonly depth: (this: void, node: RNode<ParentInformation>, idMap: Map<NodeId, RNode<ParentInformation>>) => number;
71
+ readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation>, idMap: import("../processing/decorate").AstIdMap<OtherInfo & ParentInformation>) => RNode<OtherInfo & ParentInformation> | undefined;
72
+ readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & ParentInformation> | undefined, idMap: import("../processing/decorate").AstIdMap<OtherInfo & ParentInformation>) => Generator<RNode<OtherInfo & ParentInformation>>;
73
+ readonly depth: (this: void, node: RNode<ParentInformation>, idMap: import("../processing/decorate").AstIdMap<ParentInformation>) => number;
74
74
  readonly collectAllIdsWithStop: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & ParentInformation>>, stop: (node: RNode<OtherInfo & ParentInformation>) => boolean) => Set<NodeId>;
75
75
  readonly lexeme: <R extends RNode<ParentInformation>>(this: void, node: R | undefined) => R extends {
76
76
  lexeme: string;
@@ -30,9 +30,9 @@ export declare const RBinaryOp: {
30
30
  readonly visitAst: <OtherInfo = object>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo>>, onVisit?: import("../processing/visitor").OnEnter<OtherInfo>, onExit?: import("../processing/visitor").OnExit<OtherInfo>) => void;
31
31
  readonly collectAllIds: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => Set<import("../processing/node-id").NodeId>;
32
32
  readonly directChildren: <OtherInfo>(this: void, node: RNode<OtherInfo>) => readonly (RNode<OtherInfo> | typeof import("./r-function-call").EmptyArgument)[];
33
- readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>, idMap: Map<import("../processing/node-id").NodeId, RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined;
34
- readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined, idMap: Map<import("../processing/node-id").NodeId, RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => Generator<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>;
35
- readonly depth: (this: void, node: RNode<import("../processing/decorate").ParentInformation>, idMap: Map<import("../processing/node-id").NodeId, RNode<import("../processing/decorate").ParentInformation>>) => number;
33
+ readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>, idMap: import("../processing/decorate").AstIdMap<OtherInfo & import("../processing/decorate").ParentInformation>) => RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined;
34
+ readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined, idMap: import("../processing/decorate").AstIdMap<OtherInfo & import("../processing/decorate").ParentInformation>) => Generator<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>;
35
+ readonly depth: (this: void, node: RNode<import("../processing/decorate").ParentInformation>, idMap: import("../processing/decorate").AstIdMap<import("../processing/decorate").ParentInformation>) => number;
36
36
  readonly collectAllIdsWithStop: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>, stop: (node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>) => boolean) => Set<import("../processing/node-id").NodeId>;
37
37
  readonly lexeme: <R extends RNode<import("../processing/decorate").ParentInformation>>(this: void, node: R | undefined) => R extends {
38
38
  lexeme: string;
@@ -22,9 +22,9 @@ export declare const RBreak: {
22
22
  readonly visitAst: <OtherInfo = object>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo>>, onVisit?: import("../processing/visitor").OnEnter<OtherInfo>, onExit?: import("../processing/visitor").OnExit<OtherInfo>) => void;
23
23
  readonly collectAllIds: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => Set<import("../processing/node-id").NodeId>;
24
24
  readonly directChildren: <OtherInfo>(this: void, node: RNode<OtherInfo>) => readonly (RNode<OtherInfo> | typeof import("./r-function-call").EmptyArgument)[];
25
- readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>, idMap: Map<import("../processing/node-id").NodeId, RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined;
26
- readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined, idMap: Map<import("../processing/node-id").NodeId, RNode<OtherInfo & import("../processing/decorate").ParentInformation>>) => Generator<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>;
27
- readonly depth: (this: void, node: RNode<import("../processing/decorate").ParentInformation>, idMap: Map<import("../processing/node-id").NodeId, RNode<import("../processing/decorate").ParentInformation>>) => number;
25
+ readonly directParent: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>, idMap: import("../processing/decorate").AstIdMap<OtherInfo & import("../processing/decorate").ParentInformation>) => RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined;
26
+ readonly iterateParents: <OtherInfo>(this: void, node: RNode<OtherInfo & import("../processing/decorate").ParentInformation> | undefined, idMap: import("../processing/decorate").AstIdMap<OtherInfo & import("../processing/decorate").ParentInformation>) => Generator<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>;
27
+ readonly depth: (this: void, node: RNode<import("../processing/decorate").ParentInformation>, idMap: import("../processing/decorate").AstIdMap<import("../processing/decorate").ParentInformation>) => number;
28
28
  readonly collectAllIdsWithStop: <OtherInfo>(this: void, nodes: import("../../../../../abstract-interpretation/normalized-ast-fold").SingleOrArrayOrNothing<RNode<OtherInfo & import("../processing/decorate").ParentInformation>>, stop: (node: RNode<OtherInfo & import("../processing/decorate").ParentInformation>) => boolean) => Set<import("../processing/node-id").NodeId>;
29
29
  readonly lexeme: <R extends RNode<import("../processing/decorate").ParentInformation>>(this: void, node: R | undefined) => R extends {
30
30
  lexeme: string;