@eagleoutice/flowr 2.9.2 → 2.9.4
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.
- package/README.md +18 -18
- package/cli/flowr.js +4 -2
- package/control-flow/basic-cfg-guided-visitor.js +10 -4
- package/control-flow/cfg-dead-code.js +6 -3
- package/control-flow/control-flow-graph.js +3 -2
- package/control-flow/simple-visitor.d.ts +2 -1
- package/control-flow/simple-visitor.js +6 -7
- package/core/print/slice-diff-ansi.js +3 -3
- package/dataflow/environments/built-in.d.ts +10 -1
- package/dataflow/environments/environment.js +12 -7
- package/dataflow/eval/resolve/alias-tracking.d.ts +8 -8
- package/dataflow/eval/resolve/alias-tracking.js +33 -34
- package/dataflow/eval/resolve/resolve.d.ts +7 -41
- package/dataflow/eval/resolve/resolve.js +24 -54
- package/dataflow/extractor.js +2 -2
- package/dataflow/fn/higher-order-function.d.ts +2 -1
- package/dataflow/fn/higher-order-function.js +5 -4
- package/dataflow/graph/graph.js +7 -12
- package/dataflow/internal/process/functions/call/argument/make-argument.js +1 -2
- package/dataflow/internal/process/functions/call/built-in/built-in-register-hook.js +2 -2
- package/dataflow/internal/process/functions/call/built-in/built-in-replacement.js +2 -2
- package/dataflow/internal/process/functions/call/built-in/built-in-s-seven-new-generic.js +2 -2
- package/dataflow/internal/process/functions/call/built-in/built-in-s-three-dispatch.js +1 -1
- package/documentation/doc-util/doc-search.js +2 -2
- package/linter/linter-executor.js +1 -2
- package/linter/linter-format.d.ts +37 -11
- package/linter/linter-format.js +59 -16
- package/linter/linter-rules.d.ts +8 -23
- package/linter/rules/absolute-path.d.ts +2 -2
- package/linter/rules/absolute-path.js +6 -7
- package/linter/rules/dataframe-access-validation.d.ts +1 -1
- package/linter/rules/dataframe-access-validation.js +3 -4
- package/linter/rules/dead-code.d.ts +2 -2
- package/linter/rules/dead-code.js +5 -6
- package/linter/rules/deprecated-functions.d.ts +4 -7
- package/linter/rules/file-path-validity.d.ts +2 -2
- package/linter/rules/file-path-validity.js +9 -6
- package/linter/rules/function-finder-util.d.ts +8 -11
- package/linter/rules/function-finder-util.js +21 -12
- package/linter/rules/naming-convention.d.ts +4 -11
- package/linter/rules/naming-convention.js +10 -10
- package/linter/rules/network-functions.d.ts +5 -8
- package/linter/rules/network-functions.js +14 -1
- package/linter/rules/seeded-randomness.d.ts +3 -3
- package/linter/rules/seeded-randomness.js +5 -5
- package/linter/rules/unused-definition.d.ts +2 -2
- package/linter/rules/unused-definition.js +13 -14
- package/linter/rules/useless-loop.d.ts +3 -3
- package/linter/rules/useless-loop.js +4 -4
- package/package.json +1 -1
- package/project/plugins/file-plugins/files/flowr-namespace-file.js +2 -2
- package/queries/catalog/does-call-query/does-call-query-format.js +2 -2
- package/queries/catalog/inspect-exceptions-query/inspect-exception-query-format.js +5 -4
- package/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-executor.js +6 -1
- package/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-format.d.ts +1 -1
- package/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-format.js +5 -4
- package/queries/catalog/inspect-recursion-query/inspect-recursion-query-format.js +4 -3
- package/queries/catalog/linter-query/linter-query-format.d.ts +1 -1
- package/queries/catalog/linter-query/linter-query-format.js +13 -9
- package/queries/query.d.ts +1 -1
- package/r-bridge/lang-4.x/ast/parser/main/normalize-meta.d.ts +1 -1
- package/r-bridge/lang-4.x/ast/parser/main/normalize-meta.js +2 -2
- package/r-bridge/lang-4.x/tree-sitter/tree-sitter-normalize.js +1 -1
- package/r-bridge/roxygen2/roxygen-parse.js +1 -1
- package/statistics/features/supported/defined-functions/defined-functions.d.ts +1 -1
- package/statistics/features/supported/defined-functions/defined-functions.js +4 -4
- package/statistics/features/supported/used-functions/used-functions.js +3 -3
- package/statistics/features/supported/variables/variables.js +4 -4
- package/util/mermaid/dfg.d.ts +0 -5
- package/util/mermaid/dfg.js +2 -19
- package/util/range.d.ts +137 -54
- package/util/range.js +249 -88
- package/util/version.js +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ It offers a wide variety of features, for example:
|
|
|
24
24
|
|
|
25
25
|
```shell
|
|
26
26
|
$ docker run -it --rm eagleoutice/flowr # or npm run flowr
|
|
27
|
-
flowR repl using flowR v2.9.
|
|
27
|
+
flowR repl using flowR v2.9.3, R grammar v14 (tree-sitter engine)
|
|
28
28
|
R> :query @linter "read.csv(\"/root/x.txt\")"
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -80,7 +80,7 @@ It offers a wide variety of features, for example:
|
|
|
80
80
|
|
|
81
81
|
_Results (prettified and summarized):_
|
|
82
82
|
|
|
83
|
-
Query: **linter** (
|
|
83
|
+
Query: **linter** (3 ms)\
|
|
84
84
|
╰ **Deprecated Functions** (deprecated-functions):\
|
|
85
85
|
╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 1, processTimeMs: 0</code>\
|
|
86
86
|
╰ **File Path Validity** (file-path-validity):\
|
|
@@ -88,7 +88,7 @@ It offers a wide variety of features, for example:
|
|
|
88
88
|
╰ Path `/root/x.txt` at 1.1-23\
|
|
89
89
|
╰ _Metadata_: <code>totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
90
90
|
╰ **Seeded Randomness** (seeded-randomness):\
|
|
91
|
-
╰ _Metadata_: <code>consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 0, processTimeMs:
|
|
91
|
+
╰ _Metadata_: <code>consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 0, processTimeMs: 1</code>\
|
|
92
92
|
╰ **Absolute Paths** (absolute-file-paths):\
|
|
93
93
|
╰ certain:\
|
|
94
94
|
╰ Path `/root/x.txt` at 1.1-23\
|
|
@@ -96,20 +96,20 @@ It offers a wide variety of features, for example:
|
|
|
96
96
|
╰ **Unused Definitions** (unused-definitions):\
|
|
97
97
|
╰ _Metadata_: <code>totalConsidered: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
98
98
|
╰ **Naming Convention** (naming-convention):\
|
|
99
|
-
╰ _Metadata_: <code>numMatches: 0, numBreak: 0, searchTimeMs:
|
|
99
|
+
╰ _Metadata_: <code>numMatches: 0, numBreak: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
100
100
|
╰ **Network Functions** (network-functions):\
|
|
101
101
|
╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
102
102
|
╰ **Dataframe Access Validation** (dataframe-access-validation):\
|
|
103
103
|
╰ _Metadata_: <code>numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
104
104
|
╰ **Dead Code** (dead-code):\
|
|
105
|
-
╰ _Metadata_: <code>consideredNodes: 5, searchTimeMs:
|
|
105
|
+
╰ _Metadata_: <code>consideredNodes: 5, searchTimeMs: 1, processTimeMs: 0</code>\
|
|
106
106
|
╰ **Useless Loops** (useless-loop):\
|
|
107
107
|
╰ _Metadata_: <code>numOfUselessLoops: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
108
|
-
_All queries together required ≈
|
|
108
|
+
_All queries together required ≈3 ms (1ms accuracy, total 3 ms)_
|
|
109
109
|
|
|
110
110
|
<details> <summary style="color:gray">Show Detailed Results as Json</summary>
|
|
111
111
|
|
|
112
|
-
The analysis required _2.
|
|
112
|
+
The analysis required _2.6 ms_ (including parsing and normalization and the query) within the generation environment.
|
|
113
113
|
|
|
114
114
|
In general, the JSON contains the Ids of the nodes in question as they are present in the normalized AST or the dataflow graph of flowR.
|
|
115
115
|
Please consult the [Interface](https://github.com/flowr-analysis/flowr/wiki/Interface) wiki page for more information on how to get those.
|
|
@@ -134,7 +134,7 @@ It offers a wide variety of features, for example:
|
|
|
134
134
|
"results": [
|
|
135
135
|
{
|
|
136
136
|
"involvedId": 3,
|
|
137
|
-
"
|
|
137
|
+
"loc": [
|
|
138
138
|
1,
|
|
139
139
|
1,
|
|
140
140
|
1,
|
|
@@ -162,7 +162,7 @@ It offers a wide variety of features, for example:
|
|
|
162
162
|
"callsWithNonConstantProducers": 0,
|
|
163
163
|
"callsWithOtherBranchProducers": 0,
|
|
164
164
|
"searchTimeMs": 0,
|
|
165
|
-
"processTimeMs":
|
|
165
|
+
"processTimeMs": 1
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
168
|
"absolute-file-paths": {
|
|
@@ -170,7 +170,7 @@ It offers a wide variety of features, for example:
|
|
|
170
170
|
{
|
|
171
171
|
"certainty": "certain",
|
|
172
172
|
"filePath": "/root/x.txt",
|
|
173
|
-
"
|
|
173
|
+
"loc": [
|
|
174
174
|
1,
|
|
175
175
|
1,
|
|
176
176
|
1,
|
|
@@ -198,7 +198,7 @@ It offers a wide variety of features, for example:
|
|
|
198
198
|
".meta": {
|
|
199
199
|
"numMatches": 0,
|
|
200
200
|
"numBreak": 0,
|
|
201
|
-
"searchTimeMs":
|
|
201
|
+
"searchTimeMs": 0,
|
|
202
202
|
"processTimeMs": 0
|
|
203
203
|
}
|
|
204
204
|
},
|
|
@@ -225,7 +225,7 @@ It offers a wide variety of features, for example:
|
|
|
225
225
|
"results": [],
|
|
226
226
|
".meta": {
|
|
227
227
|
"consideredNodes": 5,
|
|
228
|
-
"searchTimeMs":
|
|
228
|
+
"searchTimeMs": 1,
|
|
229
229
|
"processTimeMs": 0
|
|
230
230
|
}
|
|
231
231
|
},
|
|
@@ -239,11 +239,11 @@ It offers a wide variety of features, for example:
|
|
|
239
239
|
}
|
|
240
240
|
},
|
|
241
241
|
".meta": {
|
|
242
|
-
"timing":
|
|
242
|
+
"timing": 3
|
|
243
243
|
}
|
|
244
244
|
},
|
|
245
245
|
".meta": {
|
|
246
|
-
"timing":
|
|
246
|
+
"timing": 3
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
```
|
|
@@ -308,7 +308,7 @@ It offers a wide variety of features, for example:
|
|
|
308
308
|
|
|
309
309
|
```shell
|
|
310
310
|
$ docker run -it --rm eagleoutice/flowr # or npm run flowr
|
|
311
|
-
flowR repl using flowR v2.9.
|
|
311
|
+
flowR repl using flowR v2.9.3, R grammar v14 (tree-sitter engine)
|
|
312
312
|
R> :query @static-slice (11@sum) file://test/testfiles/example.R
|
|
313
313
|
```
|
|
314
314
|
|
|
@@ -356,7 +356,7 @@ It offers a wide variety of features, for example:
|
|
|
356
356
|
|
|
357
357
|
|
|
358
358
|
* 🚀 **fast call-graph, data-, and control-flow graphs**\
|
|
359
|
-
Within just [<i><span title="This measurement is automatically fetched from the latest benchmark!">
|
|
359
|
+
Within just [<i><span title="This measurement is automatically fetched from the latest benchmark!">121.6 ms</span></i> (as of Feb 4, 2026)](https://flowr-analysis.github.io/flowr/wiki/stats/benchmark),
|
|
360
360
|
_flowR_ can analyze the data- and control-flow of the average real-world R script. See the [benchmarks](https://flowr-analysis.github.io/flowr/wiki/stats/benchmark) for more information,
|
|
361
361
|
and consult the [wiki pages](https://github.com/flowr-analysis/flowr/wiki/wiki/dataflow-graph) for more details on the dataflow graphs as well as call graphs.
|
|
362
362
|
|
|
@@ -392,7 +392,7 @@ It offers a wide variety of features, for example:
|
|
|
392
392
|
|
|
393
393
|
```shell
|
|
394
394
|
$ docker run -it --rm eagleoutice/flowr # or npm run flowr
|
|
395
|
-
flowR repl using flowR v2.9.
|
|
395
|
+
flowR repl using flowR v2.9.3, R grammar v14 (tree-sitter engine)
|
|
396
396
|
R> :dataflow* test/testfiles/example.R
|
|
397
397
|
```
|
|
398
398
|
|
|
@@ -697,7 +697,7 @@ It offers a wide variety of features, for example:
|
|
|
697
697
|
```
|
|
698
698
|
|
|
699
699
|
|
|
700
|
-
(The analysis required
|
|
700
|
+
(The analysis required _2.3 ms_ (including parse and normalize, using the [tree-sitter](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)
|
|
701
701
|
|
|
702
702
|
|
|
703
703
|
|
package/cli/flowr.js
CHANGED
|
@@ -105,8 +105,10 @@ function hookSignalHandlers(engines) {
|
|
|
105
105
|
async function mainRepl() {
|
|
106
106
|
const config = createConfig();
|
|
107
107
|
if (options.script) {
|
|
108
|
-
const
|
|
109
|
-
(0, assert_1.guard)(
|
|
108
|
+
const script = scripts_info_1.scripts[options.script];
|
|
109
|
+
(0, assert_1.guard)(script !== undefined, `Unknown script target "${options.script}", pick one of ${(0, flowr_main_options_1.getScriptsText)()}.`);
|
|
110
|
+
const target = script.target;
|
|
111
|
+
(0, assert_1.guard)(target !== undefined, `Unknown script target "${options.script}", pick one of ${(0, flowr_main_options_1.getScriptsText)()}.`);
|
|
110
112
|
console.log(`Running script '${ansi_1.formatter.format(options.script, { style: 1 /* FontStyles.Bold */ })}'`);
|
|
111
113
|
log_1.log.debug(`Script maps to "${target}"`);
|
|
112
114
|
await (0, execute_1.waitOnScript)(`${__dirname}/${target}`, process.argv.slice(3), undefined, true);
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.BasicCfgGuidedVisitor = void 0;
|
|
4
4
|
const control_flow_graph_1 = require("./control-flow-graph");
|
|
5
5
|
const assert_1 = require("../util/assert");
|
|
6
|
+
const invert_cfg_1 = require("./invert-cfg");
|
|
6
7
|
/**
|
|
7
8
|
* In contrast to {@link visitCfgInOrder} and {@link visitCfgInReverseOrder}, this visitor is not a simple visitor
|
|
8
9
|
* and serves as the basis for a variety of more complicated visiting orders of the control flow graph.
|
|
@@ -31,10 +32,15 @@ class BasicCfgGuidedVisitor {
|
|
|
31
32
|
}
|
|
32
33
|
startVisitor(start) {
|
|
33
34
|
const graph = this.config.controlFlow.graph;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
(
|
|
37
|
-
|
|
35
|
+
let getNext;
|
|
36
|
+
if (this.config.defaultVisitingOrder === 'forward') {
|
|
37
|
+
const inverseGraph = (0, invert_cfg_1.invertCfg)(graph);
|
|
38
|
+
getNext = (node) => inverseGraph.outgoingEdges(node)?.keys().toArray().reverse();
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
getNext = (node) => graph.outgoingEdges(node)?.keys();
|
|
42
|
+
}
|
|
43
|
+
const stack = Array.from(start);
|
|
38
44
|
while (stack.length > 0) {
|
|
39
45
|
const current = stack.pop();
|
|
40
46
|
if (!this.visitNode(current)) {
|
|
@@ -9,10 +9,12 @@ const r_function_call_1 = require("../r-bridge/lang-4.x/ast/model/nodes/r-functi
|
|
|
9
9
|
const general_1 = require("../dataflow/eval/values/general");
|
|
10
10
|
const r_value_1 = require("../dataflow/eval/values/r-value");
|
|
11
11
|
const simple_visitor_1 = require("./simple-visitor");
|
|
12
|
+
const invert_cfg_1 = require("./invert-cfg");
|
|
12
13
|
class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.SemanticCfgGuidedVisitor {
|
|
13
14
|
cachedConditions = new Map();
|
|
14
15
|
cachedStatements = new Map();
|
|
15
16
|
inTry = new Set();
|
|
17
|
+
invertedCfg;
|
|
16
18
|
getValue(id) {
|
|
17
19
|
const has = this.cachedConditions.get(id);
|
|
18
20
|
if (has) {
|
|
@@ -39,6 +41,7 @@ class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.Semant
|
|
|
39
41
|
this.cachedConditions.set(id, value ? logic_1.Ternary.Always : logic_1.Ternary.Never);
|
|
40
42
|
}
|
|
41
43
|
startVisitor() {
|
|
44
|
+
this.invertedCfg = (0, invert_cfg_1.invertCfg)(this.config.controlFlow.graph);
|
|
42
45
|
for (const [from, targets] of this.config.controlFlow.graph.edges()) {
|
|
43
46
|
for (const [target, edge] of targets) {
|
|
44
47
|
if (edge.label === 1 /* CfgEdgeType.Cd */) {
|
|
@@ -53,7 +56,7 @@ class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.Semant
|
|
|
53
56
|
else if (edge.label === 0 /* CfgEdgeType.Fd */ && this.isUnconditionalJump(target)) {
|
|
54
57
|
// for each unconditional jump, we find the corresponding end/exit nodes and remove any flow edges
|
|
55
58
|
for (const end of this.getCfgVertex(target)?.end ?? []) {
|
|
56
|
-
for (const [target, edge] of this.
|
|
59
|
+
for (const [target, edge] of this.invertedCfg.outgoingEdges(end) ?? []) {
|
|
57
60
|
if (edge.label === 0 /* CfgEdgeType.Fd */) {
|
|
58
61
|
this.config.controlFlow.graph.removeEdge(target, end);
|
|
59
62
|
}
|
|
@@ -130,7 +133,7 @@ class CfgConditionalDeadCodeRemoval extends semantic_cfg_guided_visitor_1.Semant
|
|
|
130
133
|
}
|
|
131
134
|
this.inTry.add(n);
|
|
132
135
|
return false;
|
|
133
|
-
});
|
|
136
|
+
}, this.invertedCfg);
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
/** Breaks unsatisfiable control dependencies */
|
|
@@ -144,7 +147,7 @@ function cfgAnalyzeDeadCode(cfg, info) {
|
|
|
144
147
|
normalizedAst: info.ast,
|
|
145
148
|
dfg: info.dfg,
|
|
146
149
|
ctx: info.ctx,
|
|
147
|
-
defaultVisitingOrder: '
|
|
150
|
+
defaultVisitingOrder: 'backward'
|
|
148
151
|
});
|
|
149
152
|
visitor.start();
|
|
150
153
|
return cfg;
|
|
@@ -132,8 +132,9 @@ class ControlFlowGraph {
|
|
|
132
132
|
ingoingEdges(id) {
|
|
133
133
|
const edges = new Map();
|
|
134
134
|
for (const [source, outgoing] of this.edgeInformation.entries()) {
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
const o = outgoing.get(id);
|
|
136
|
+
if (o) {
|
|
137
|
+
edges.set(source, o);
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
return edges;
|
|
@@ -16,11 +16,12 @@ export declare function visitCfgInReverseOrder(graph: ControlFlowGraph, startNod
|
|
|
16
16
|
* @param graph - The control flow graph.
|
|
17
17
|
* @param startNodes - The nodes to start the traversal from.
|
|
18
18
|
* @param visitor - The visitor function to call for each node, if you return true the traversal from this node will be stopped.
|
|
19
|
+
* @param invertedCfg - Optionally provide an inverted control flow graph, if not provided the function will create one by inverting the given graph, which can be expensive for large graphs.
|
|
19
20
|
*
|
|
20
21
|
* This function is of type {@link SimpleCfgVisitor}.
|
|
21
22
|
* @see {@link visitCfgInReverseOrder} for a traversal in reversed order
|
|
22
23
|
*/
|
|
23
|
-
export declare function visitCfgInOrder(graph: ControlFlowGraph, startNodes: readonly NodeId[], visitor: (node: NodeId) => boolean | void): Set<NodeId>;
|
|
24
|
+
export declare function visitCfgInOrder(graph: ControlFlowGraph, startNodes: readonly NodeId[], visitor: (node: NodeId) => boolean | void, invertedCfg?: ControlFlowGraph): Set<NodeId>;
|
|
24
25
|
/**
|
|
25
26
|
* Check if a node can reach another node in the control flow graph.
|
|
26
27
|
*/
|
|
@@ -35,11 +35,9 @@ visitor) {
|
|
|
35
35
|
queue = queue.concat(get.elems.toReversed().map(e => e.id));
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
const incoming = graph.outgoingEdges(current);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
queue.push(c);
|
|
42
|
-
}
|
|
38
|
+
const incoming = graph.outgoingEdges(current) ?? [];
|
|
39
|
+
for (const c of incoming.keys()) {
|
|
40
|
+
queue.push(c);
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
}
|
|
@@ -48,17 +46,18 @@ visitor) {
|
|
|
48
46
|
* @param graph - The control flow graph.
|
|
49
47
|
* @param startNodes - The nodes to start the traversal from.
|
|
50
48
|
* @param visitor - The visitor function to call for each node, if you return true the traversal from this node will be stopped.
|
|
49
|
+
* @param invertedCfg - Optionally provide an inverted control flow graph, if not provided the function will create one by inverting the given graph, which can be expensive for large graphs.
|
|
51
50
|
*
|
|
52
51
|
* This function is of type {@link SimpleCfgVisitor}.
|
|
53
52
|
* @see {@link visitCfgInReverseOrder} for a traversal in reversed order
|
|
54
53
|
*/
|
|
55
54
|
function visitCfgInOrder(graph, startNodes,
|
|
56
55
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- void is used to indicate that the return value is ignored/we never stop
|
|
57
|
-
visitor) {
|
|
56
|
+
visitor, invertedCfg) {
|
|
58
57
|
const visited = new Set();
|
|
59
58
|
let queue = startNodes.slice();
|
|
60
59
|
const hasBb = graph.mayHaveBasicBlocks();
|
|
61
|
-
const g = (0, invert_cfg_1.invertCfg)(graph);
|
|
60
|
+
const g = invertedCfg ?? (0, invert_cfg_1.invertCfg)(graph);
|
|
62
61
|
while (queue.length > 0) {
|
|
63
62
|
const current = queue.shift();
|
|
64
63
|
if (visited.has(current)) {
|
|
@@ -9,11 +9,11 @@ function grayOut() {
|
|
|
9
9
|
}
|
|
10
10
|
function mergeJointRangesInSorted(loc) {
|
|
11
11
|
return loc.reduce((acc, curr) => {
|
|
12
|
-
if (
|
|
12
|
+
if (range_1.SourceRange.overlap(acc[acc.length - 1].location, curr.location)) {
|
|
13
13
|
return [
|
|
14
14
|
...acc.slice(0, -1), {
|
|
15
15
|
selected: curr.selected || acc[acc.length - 1].selected,
|
|
16
|
-
location:
|
|
16
|
+
location: range_1.SourceRange.merge([acc[acc.length - 1].location, curr.location])
|
|
17
17
|
}
|
|
18
18
|
];
|
|
19
19
|
}
|
|
@@ -37,7 +37,7 @@ function sliceDiffAnsi(slice, normalized, criteriaIds, originalCode) {
|
|
|
37
37
|
return `${grayOut()}${originalCode}${ansi_1.ansiFormatter.reset()}`;
|
|
38
38
|
}
|
|
39
39
|
// we sort all locations from back to front so that replacements do not screw up the indices
|
|
40
|
-
importantLocations.sort((a, b) => -
|
|
40
|
+
importantLocations.sort((a, b) => -range_1.SourceRange.compare(a.location, b.location));
|
|
41
41
|
// we need to merge all ranges that overlap, otherwise even reversed traversal can still crew us up
|
|
42
42
|
importantLocations = mergeJointRangesInSorted(importantLocations);
|
|
43
43
|
const lines = originalCode.split('\n');
|
|
@@ -83,7 +83,16 @@ export interface DefaultBuiltInProcessorConfiguration extends ForceArguments {
|
|
|
83
83
|
*/
|
|
84
84
|
readonly useAsProcessor?: BuiltInProcName;
|
|
85
85
|
}
|
|
86
|
-
export
|
|
86
|
+
export interface BuiltInEvalHandlerArgs {
|
|
87
|
+
resolve: VariableResolve;
|
|
88
|
+
node: RNodeWithParent;
|
|
89
|
+
ctx: ReadOnlyFlowrAnalyzerContext;
|
|
90
|
+
environment?: REnvironmentInformation;
|
|
91
|
+
graph?: DataflowGraph;
|
|
92
|
+
idMap?: AstIdMap;
|
|
93
|
+
blocked?: Set<NodeId>;
|
|
94
|
+
}
|
|
95
|
+
export type BuiltInEvalHandler = (args: BuiltInEvalHandlerArgs) => Value;
|
|
87
96
|
declare function defaultBuiltInProcessor<OtherInfo>(name: RSymbol<OtherInfo & ParentInformation>, args: readonly RFunctionArgument<OtherInfo & ParentInformation>[], rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, { returnsNthArgument, useAsProcessor, forceArgs, readAllArguments, cfg, hasUnknownSideEffects, treatAsFnCall }: DefaultBuiltInProcessorConfiguration): DataflowInformation;
|
|
88
97
|
/**
|
|
89
98
|
* This contains all names of built-in function handlers and origins
|
|
@@ -110,7 +110,7 @@ class Environment {
|
|
|
110
110
|
}
|
|
111
111
|
// navigate to parent until either before built-in or matching namespace
|
|
112
112
|
const newEnvironment = this.clone(false);
|
|
113
|
-
|
|
113
|
+
let current = newEnvironment;
|
|
114
114
|
do {
|
|
115
115
|
if (current.n === ns) {
|
|
116
116
|
current.define(definition);
|
|
@@ -119,6 +119,7 @@ class Environment {
|
|
|
119
119
|
else if (current.parent && !current.parent.builtInEnv) {
|
|
120
120
|
// clone parent
|
|
121
121
|
current.parent = current.parent.clone(false);
|
|
122
|
+
current = current.parent;
|
|
122
123
|
}
|
|
123
124
|
else {
|
|
124
125
|
break;
|
|
@@ -163,7 +164,7 @@ class Environment {
|
|
|
163
164
|
* This always recurses parents.
|
|
164
165
|
*/
|
|
165
166
|
overwrite(other, applyCds) {
|
|
166
|
-
if (
|
|
167
|
+
if (this.builtInEnv || this === other || !other) {
|
|
167
168
|
return this;
|
|
168
169
|
}
|
|
169
170
|
const map = new Map(this.memory);
|
|
@@ -171,25 +172,29 @@ class Environment {
|
|
|
171
172
|
const hasMaybe = applyCds === undefined ? values.length === 0 || values.some(v => v.cds !== undefined) : true;
|
|
172
173
|
if (hasMaybe) {
|
|
173
174
|
const old = map.get(key);
|
|
175
|
+
if (!old && applyCds === undefined) {
|
|
176
|
+
map.set(key, values);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
174
179
|
// we need to make a copy to avoid side effects for old reference in other environments
|
|
175
|
-
const
|
|
180
|
+
const updated = old?.slice() ?? [];
|
|
176
181
|
for (const v of values) {
|
|
177
182
|
const { nodeId, definedAt } = v;
|
|
178
|
-
const index =
|
|
183
|
+
const index = updated.find(o => o.nodeId === nodeId && o.definedAt === definedAt);
|
|
179
184
|
if (index) {
|
|
180
185
|
continue;
|
|
181
186
|
}
|
|
182
187
|
if (applyCds === undefined) {
|
|
183
|
-
|
|
188
|
+
updated.push(v);
|
|
184
189
|
}
|
|
185
190
|
else {
|
|
186
|
-
|
|
191
|
+
updated.push({
|
|
187
192
|
...v,
|
|
188
193
|
cds: v.cds ? applyCds.concat(v.cds) : applyCds.slice()
|
|
189
194
|
});
|
|
190
195
|
}
|
|
191
196
|
}
|
|
192
|
-
map.set(key,
|
|
197
|
+
map.set(key, updated);
|
|
193
198
|
}
|
|
194
199
|
else {
|
|
195
200
|
map.set(key, values);
|
|
@@ -2,7 +2,7 @@ import { VariableResolve } from '../../../config';
|
|
|
2
2
|
import type { AstIdMap, RNodeWithParent } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate';
|
|
3
3
|
import { type NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
|
|
4
4
|
import { type REnvironmentInformation } from '../../environments/environment';
|
|
5
|
-
import {
|
|
5
|
+
import { Identifier } from '../../environments/identifier';
|
|
6
6
|
import type { DataflowGraph } from '../../graph/graph';
|
|
7
7
|
import { type Lift, type Value, type ValueSet } from '../values/r-value';
|
|
8
8
|
import type { ReadOnlyFlowrAnalyzerContext } from '../../../project/context/flowr-analyzer-context';
|
|
@@ -20,6 +20,8 @@ export interface ResolveInfo {
|
|
|
20
20
|
resolve?: VariableResolve;
|
|
21
21
|
/** Context used for resolving */
|
|
22
22
|
ctx: ReadOnlyFlowrAnalyzerContext;
|
|
23
|
+
/** If set, the ids that should not be considered during resolution (=> top) */
|
|
24
|
+
blocked?: Set<NodeId>;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* Gets the definitions / aliases of a node
|
|
@@ -54,22 +56,20 @@ export declare function getAliases(sourceIds: readonly NodeId[], dataflow: Dataf
|
|
|
54
56
|
* @param full - Whether to track aliases on resolve
|
|
55
57
|
* @param resolve - Variable resolve mode
|
|
56
58
|
* @param ctx - Context used for clean environment
|
|
59
|
+
* @param blocked - If set, the ids that should not be considered during resolution (=>top)
|
|
57
60
|
*/
|
|
58
|
-
export declare function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { environment, graph, idMap, full, resolve, ctx }: ResolveInfo): ResolveResult;
|
|
61
|
+
export declare function resolveIdToValue(id: NodeId | RNodeWithParent | undefined, { environment, graph, idMap, full, resolve, ctx, blocked }: ResolveInfo): ResolveResult;
|
|
59
62
|
/**
|
|
60
63
|
* Please use {@link resolveIdToValue}
|
|
61
64
|
*
|
|
62
65
|
* Uses the aliases that were tracked in the environments (by the
|
|
63
66
|
* {@link getAliases} function) to resolve a node to a value.
|
|
64
|
-
* @param resolve - Variable resolve mode
|
|
65
67
|
* @param identifier - Identifier to resolve
|
|
66
|
-
* @param
|
|
67
|
-
* @param
|
|
68
|
-
* @param graph - dataflow graph
|
|
69
|
-
* @param idMap - id map of Dataflow graph
|
|
68
|
+
* @param environment - Environment to use
|
|
69
|
+
* @param r - Resolve information (env, ctx, ...)
|
|
70
70
|
* @returns Value of Identifier or Top
|
|
71
71
|
*/
|
|
72
|
-
export declare function trackAliasInEnvironments(
|
|
72
|
+
export declare function trackAliasInEnvironments(identifier: Identifier | undefined, environment: REnvironmentInformation, { blocked, idMap, resolve, ctx, graph }: Omit<ResolveInfo, 'environment'>): ResolveResult;
|
|
73
73
|
/**
|
|
74
74
|
* Please use {@link resolveIdToValue}
|
|
75
75
|
*
|
|
@@ -117,39 +117,44 @@ function getAliases(sourceIds, dataflow, environment) {
|
|
|
117
117
|
* @param full - Whether to track aliases on resolve
|
|
118
118
|
* @param resolve - Variable resolve mode
|
|
119
119
|
* @param ctx - Context used for clean environment
|
|
120
|
+
* @param blocked - If set, the ids that should not be considered during resolution (=>top)
|
|
120
121
|
*/
|
|
121
|
-
function resolveIdToValue(id, { environment, graph, idMap, full = true, resolve, ctx }) {
|
|
122
|
+
function resolveIdToValue(id, { environment, graph, idMap, full = true, resolve, ctx, blocked }) {
|
|
122
123
|
const variableResolve = resolve ?? ctx.config.solver.variables;
|
|
124
|
+
blocked ??= new Set();
|
|
123
125
|
if (id === undefined) {
|
|
124
126
|
return r_value_1.Top;
|
|
125
127
|
}
|
|
126
128
|
idMap ??= graph?.idMap;
|
|
127
129
|
const node = typeof id === 'object' ? id : idMap?.get(id);
|
|
128
|
-
if (node === undefined) {
|
|
130
|
+
if (node === undefined || blocked.has(node.info.id)) {
|
|
129
131
|
return r_value_1.Top;
|
|
130
132
|
}
|
|
133
|
+
blocked.add(node.info.id);
|
|
131
134
|
switch (node.type) {
|
|
132
135
|
case type_1.RType.Argument:
|
|
133
136
|
if (node.value) {
|
|
134
|
-
return resolveIdToValue(node.value.info.id, { environment, graph, idMap, full, resolve: variableResolve, ctx });
|
|
137
|
+
return resolveIdToValue(node.value.info.id, { environment, graph, idMap, full, resolve: variableResolve, ctx, blocked });
|
|
135
138
|
}
|
|
136
139
|
// eslint-disable-next-line no-fallthrough
|
|
137
140
|
case type_1.RType.Symbol:
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return r_value_1.Top;
|
|
141
|
+
if (full) {
|
|
142
|
+
if (environment) {
|
|
143
|
+
return trackAliasInEnvironments(identifier_1.Identifier.toString(node.content), environment, { idMap, resolve: variableResolve, ctx, graph, blocked });
|
|
144
|
+
}
|
|
145
|
+
else if (graph && resolve === config_1.VariableResolve.Alias) {
|
|
146
|
+
return trackAliasesInGraph(node.info.id, graph, ctx, idMap);
|
|
147
|
+
}
|
|
146
148
|
}
|
|
149
|
+
return r_value_1.Top;
|
|
147
150
|
case type_1.RType.FunctionDefinition:
|
|
148
151
|
return (0, set_constants_1.setFrom)({ type: 'function-definition' });
|
|
149
152
|
case type_1.RType.FunctionCall:
|
|
150
153
|
case type_1.RType.BinaryOp:
|
|
151
154
|
case type_1.RType.UnaryOp:
|
|
152
|
-
return (0, set_constants_1.setFrom)((0, resolve_1.resolveNode)(
|
|
155
|
+
return (0, set_constants_1.setFrom)((0, resolve_1.resolveNode)({
|
|
156
|
+
resolve: variableResolve, node, ctx, environment, graph, idMap, blocked
|
|
157
|
+
}));
|
|
153
158
|
case type_1.RType.String:
|
|
154
159
|
case type_1.RType.Number:
|
|
155
160
|
case type_1.RType.Logical:
|
|
@@ -163,19 +168,16 @@ function resolveIdToValue(id, { environment, graph, idMap, full = true, resolve,
|
|
|
163
168
|
*
|
|
164
169
|
* Uses the aliases that were tracked in the environments (by the
|
|
165
170
|
* {@link getAliases} function) to resolve a node to a value.
|
|
166
|
-
* @param resolve - Variable resolve mode
|
|
167
171
|
* @param identifier - Identifier to resolve
|
|
168
|
-
* @param
|
|
169
|
-
* @param
|
|
170
|
-
* @param graph - dataflow graph
|
|
171
|
-
* @param idMap - id map of Dataflow graph
|
|
172
|
+
* @param environment - Environment to use
|
|
173
|
+
* @param r - Resolve information (env, ctx, ...)
|
|
172
174
|
* @returns Value of Identifier or Top
|
|
173
175
|
*/
|
|
174
|
-
function trackAliasInEnvironments(
|
|
176
|
+
function trackAliasInEnvironments(identifier, environment, { blocked, idMap, resolve = config_1.VariableResolve.Alias, ctx, graph }) {
|
|
175
177
|
if (identifier === undefined) {
|
|
176
178
|
return r_value_1.Top;
|
|
177
179
|
}
|
|
178
|
-
const defs = (0, resolve_by_name_1.resolveByNameAnyType)(identifier,
|
|
180
|
+
const defs = (0, resolve_by_name_1.resolveByNameAnyType)(identifier, environment);
|
|
179
181
|
if (defs === undefined) {
|
|
180
182
|
return r_value_1.Top;
|
|
181
183
|
}
|
|
@@ -195,7 +197,7 @@ function trackAliasInEnvironments(resolve, identifier, use, ctx, graph, idMap) {
|
|
|
195
197
|
for (const alias of def.value) {
|
|
196
198
|
const definitionOfAlias = idMap?.get(alias);
|
|
197
199
|
if (definitionOfAlias !== undefined) {
|
|
198
|
-
const value = (0, resolve_1.resolveNode)(resolve, definitionOfAlias, ctx,
|
|
200
|
+
const value = (0, resolve_1.resolveNode)({ resolve, node: definitionOfAlias, ctx, environment, graph, idMap, blocked });
|
|
199
201
|
if ((0, r_value_1.isTop)(value)) {
|
|
200
202
|
return r_value_1.Top;
|
|
201
203
|
}
|
|
@@ -252,7 +254,7 @@ function isNestedInLoop(node, ast) {
|
|
|
252
254
|
if (parentNode === undefined) {
|
|
253
255
|
return false;
|
|
254
256
|
}
|
|
255
|
-
if (parentNode.type === type_1.RType.WhileLoop || parentNode.type === type_1.RType.RepeatLoop) {
|
|
257
|
+
if (parentNode.type === type_1.RType.WhileLoop || parentNode.type === type_1.RType.RepeatLoop || parentNode.type === type_1.RType.ForLoop) {
|
|
256
258
|
return true;
|
|
257
259
|
}
|
|
258
260
|
return isNestedInLoop(parentNode, ast);
|
|
@@ -273,7 +275,7 @@ function trackAliasesInGraph(id, graph, ctx, idMap) {
|
|
|
273
275
|
}
|
|
274
276
|
idMap ??= graph.idMap;
|
|
275
277
|
(0, assert_1.guard)(idMap !== undefined, 'The ID map is required to get the lineage of a node');
|
|
276
|
-
const queue = new visiting_queue_1.VisitingQueue(
|
|
278
|
+
const queue = new visiting_queue_1.VisitingQueue(10);
|
|
277
279
|
const clean = ctx.env.makeCleanEnv();
|
|
278
280
|
const cleanFingerprint = ctx.env.getCleanEnvFingerprint();
|
|
279
281
|
queue.add(id, clean, cleanFingerprint, false);
|
|
@@ -281,11 +283,10 @@ function trackAliasesInGraph(id, graph, ctx, idMap) {
|
|
|
281
283
|
const resultIds = [];
|
|
282
284
|
while (queue.nonEmpty()) {
|
|
283
285
|
const { id, baseEnvironment } = queue.next();
|
|
284
|
-
const
|
|
285
|
-
if (!
|
|
286
|
+
const vertex = graph.getVertex(id);
|
|
287
|
+
if (!vertex) {
|
|
286
288
|
continue;
|
|
287
289
|
}
|
|
288
|
-
const [vertex, outgoingEdges] = res;
|
|
289
290
|
const cds = vertex.cds;
|
|
290
291
|
for (const cd of cds ?? []) {
|
|
291
292
|
const target = graph.idMap?.get(cd.id);
|
|
@@ -303,25 +304,23 @@ function trackAliasesInGraph(id, graph, ctx, idMap) {
|
|
|
303
304
|
if (forceTop) {
|
|
304
305
|
break;
|
|
305
306
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
else if (vertex.tag === vertex_1.VertexType.FunctionDefinition) {
|
|
307
|
+
const t = vertex.tag;
|
|
308
|
+
if (t === vertex_1.VertexType.Value || t === vertex_1.VertexType.FunctionDefinition) {
|
|
311
309
|
resultIds.push(id);
|
|
312
310
|
continue;
|
|
313
311
|
}
|
|
314
|
-
const isFn =
|
|
312
|
+
const isFn = t === vertex_1.VertexType.FunctionCall;
|
|
313
|
+
const outgoingEdges = graph.outgoingEdges(id) ?? [];
|
|
315
314
|
// travel all read and defined-by edges
|
|
316
|
-
for (const [targetId,
|
|
315
|
+
for (const [targetId, { types }] of outgoingEdges) {
|
|
317
316
|
if (isFn) {
|
|
318
|
-
if (
|
|
317
|
+
if (types === edge_1.EdgeType.Returns || types === edge_1.EdgeType.DefinedByOnCall || types === edge_1.EdgeType.DefinedBy) {
|
|
319
318
|
queue.add(targetId, baseEnvironment, cleanFingerprint, false);
|
|
320
319
|
}
|
|
321
320
|
continue;
|
|
322
321
|
}
|
|
323
322
|
// currently, they have to be exact!
|
|
324
|
-
if (
|
|
323
|
+
if (types === edge_1.EdgeType.Reads || types === edge_1.EdgeType.DefinedBy || types === edge_1.EdgeType.DefinedByOnCall) {
|
|
325
324
|
queue.add(targetId, baseEnvironment, cleanFingerprint, false);
|
|
326
325
|
}
|
|
327
326
|
}
|