@eagleoutice/flowr 2.9.3 → 2.9.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.
- package/README.md +13 -13
- 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/dataflow/environments/append.js +9 -2
- package/dataflow/environments/environment.js +13 -8
- package/dataflow/environments/overwrite.js +9 -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 +3 -2
- package/package.json +1 -1
- package/queries/catalog/inspect-higher-order-query/inspect-higher-order-query-executor.js +6 -1
- 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.4, R grammar v14 (tree-sitter engine)
|
|
28
28
|
R> :query @linter "read.csv(\"/root/x.txt\")"
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -82,17 +82,17 @@ It offers a wide variety of features, for example:
|
|
|
82
82
|
|
|
83
83
|
Query: **linter** (3 ms)\
|
|
84
84
|
╰ **Deprecated Functions** (deprecated-functions):\
|
|
85
|
-
╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs:
|
|
85
|
+
╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
86
86
|
╰ **File Path Validity** (file-path-validity):\
|
|
87
87
|
╰ certain:\
|
|
88
88
|
╰ Path `/root/x.txt` at 1.1-23\
|
|
89
|
-
╰ _Metadata_: <code>totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs:
|
|
89
|
+
╰ _Metadata_: <code>totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs: 1, processTimeMs: 0</code>\
|
|
90
90
|
╰ **Seeded Randomness** (seeded-randomness):\
|
|
91
91
|
╰ _Metadata_: <code>consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
92
92
|
╰ **Absolute Paths** (absolute-file-paths):\
|
|
93
93
|
╰ certain:\
|
|
94
94
|
╰ Path `/root/x.txt` at 1.1-23\
|
|
95
|
-
╰ _Metadata_: <code>totalConsidered: 1, totalUnknown: 0, searchTimeMs:
|
|
95
|
+
╰ _Metadata_: <code>totalConsidered: 1, totalUnknown: 0, searchTimeMs: 1, processTimeMs: 0</code>\
|
|
96
96
|
╰ **Unused Definitions** (unused-definitions):\
|
|
97
97
|
╰ _Metadata_: <code>totalConsidered: 0, searchTimeMs: 0, processTimeMs: 0</code>\
|
|
98
98
|
╰ **Naming Convention** (naming-convention):\
|
|
@@ -109,7 +109,7 @@ It offers a wide variety of features, for example:
|
|
|
109
109
|
|
|
110
110
|
<details> <summary style="color:gray">Show Detailed Results as Json</summary>
|
|
111
111
|
|
|
112
|
-
The analysis required
|
|
112
|
+
The analysis required _3.5 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.
|
|
@@ -126,7 +126,7 @@ It offers a wide variety of features, for example:
|
|
|
126
126
|
".meta": {
|
|
127
127
|
"totalCalls": 0,
|
|
128
128
|
"totalFunctionDefinitions": 0,
|
|
129
|
-
"searchTimeMs":
|
|
129
|
+
"searchTimeMs": 0,
|
|
130
130
|
"processTimeMs": 0
|
|
131
131
|
}
|
|
132
132
|
},
|
|
@@ -149,7 +149,7 @@ It offers a wide variety of features, for example:
|
|
|
149
149
|
"totalUnknown": 0,
|
|
150
150
|
"totalWritesBeforeAlways": 0,
|
|
151
151
|
"totalValid": 0,
|
|
152
|
-
"searchTimeMs":
|
|
152
|
+
"searchTimeMs": 1,
|
|
153
153
|
"processTimeMs": 0
|
|
154
154
|
}
|
|
155
155
|
},
|
|
@@ -181,7 +181,7 @@ It offers a wide variety of features, for example:
|
|
|
181
181
|
".meta": {
|
|
182
182
|
"totalConsidered": 1,
|
|
183
183
|
"totalUnknown": 0,
|
|
184
|
-
"searchTimeMs":
|
|
184
|
+
"searchTimeMs": 1,
|
|
185
185
|
"processTimeMs": 0
|
|
186
186
|
}
|
|
187
187
|
},
|
|
@@ -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.4, R grammar v14 (tree-sitter engine)
|
|
312
312
|
R> :query @static-slice (11@sum) file://test/testfiles/example.R
|
|
313
313
|
```
|
|
314
314
|
|
|
@@ -322,7 +322,7 @@ It offers a wide variety of features, for example:
|
|
|
322
322
|
N <- 10
|
|
323
323
|
for(i in 1:(N-1)) sum <- sum + i + w
|
|
324
324
|
sum
|
|
325
|
-
All queries together required ≈3 ms (1ms accuracy, total
|
|
325
|
+
All queries together required ≈3 ms (1ms accuracy, total 4 ms)
|
|
326
326
|
```
|
|
327
327
|
|
|
328
328
|
|
|
@@ -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!">111.1 ms</span></i> (as of Feb 5, 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.4, 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 _4.5 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)) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.uniqueMergeValuesInDefinitions = uniqueMergeValuesInDefinitions;
|
|
4
4
|
exports.appendEnvironment = appendEnvironment;
|
|
5
|
-
const
|
|
5
|
+
const scoping_1 = require("./scoping");
|
|
6
6
|
/**
|
|
7
7
|
* Merges two arrays of identifier definitions, ensuring uniqueness based on `nodeId` and `definedAt`.
|
|
8
8
|
*/
|
|
@@ -23,7 +23,14 @@ function appendEnvironment(base, next) {
|
|
|
23
23
|
else if (next === undefined) {
|
|
24
24
|
return base;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
if (base.level !== next.level) {
|
|
27
|
+
while (next.level < base.level) {
|
|
28
|
+
next = (0, scoping_1.pushLocalEnvironment)(next);
|
|
29
|
+
}
|
|
30
|
+
while (next.level > base.level) {
|
|
31
|
+
base = (0, scoping_1.pushLocalEnvironment)(base);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
27
34
|
return {
|
|
28
35
|
current: base.current.append(next.current),
|
|
29
36
|
level: base.level,
|
|
@@ -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 (!other || this.
|
|
167
|
+
if (this.builtInEnv || this === other || !other || this.n !== other.n) {
|
|
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);
|
|
@@ -206,7 +211,7 @@ class Environment {
|
|
|
206
211
|
* This always recurses parents.
|
|
207
212
|
*/
|
|
208
213
|
append(other) {
|
|
209
|
-
if (!other || this.builtInEnv) {
|
|
214
|
+
if (!other || this.builtInEnv || this === other || this.n !== other.n) {
|
|
210
215
|
return this;
|
|
211
216
|
}
|
|
212
217
|
const map = new Map(this.memory);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.overwriteEnvironment = overwriteEnvironment;
|
|
4
|
-
const
|
|
4
|
+
const scoping_1 = require("./scoping");
|
|
5
5
|
/**
|
|
6
6
|
* Assumes, that all definitions within next replace those within base (given the same name).
|
|
7
7
|
* <b>But</b> if all definitions within next are maybe, then they are appended to the base definitions (updating them to be `maybe` from now on as well), similar to {@link appendEnvironment}.
|
|
@@ -14,7 +14,14 @@ function overwriteEnvironment(base, next, applyCds) {
|
|
|
14
14
|
else if (next === undefined) {
|
|
15
15
|
return base;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
if (base.level !== next.level) {
|
|
18
|
+
while (next.level < base.level) {
|
|
19
|
+
next = (0, scoping_1.pushLocalEnvironment)(next);
|
|
20
|
+
}
|
|
21
|
+
while (next.level > base.level) {
|
|
22
|
+
base = (0, scoping_1.pushLocalEnvironment)(base);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
18
25
|
return {
|
|
19
26
|
current: base.current.overwrite(next.current, applyCds),
|
|
20
27
|
level: base.level
|
|
@@ -6,5 +6,6 @@ import type { ReadOnlyFlowrAnalyzerContext } from '../../project/context/flowr-a
|
|
|
6
6
|
* either takes a function as an argument or (may) returns a function.
|
|
7
7
|
* If the return is an identity, e.g., `function(x) x`, this is not considered higher-order,
|
|
8
8
|
* if no function is passed as an argument.
|
|
9
|
+
* Please note that inspecting higher order functions can be sped up (if queries multiple times) by providing an inverted graph as well!
|
|
9
10
|
*/
|
|
10
|
-
export declare function isFunctionHigherOrder(id: NodeId, graph: DataflowGraph, ctx: ReadOnlyFlowrAnalyzerContext): boolean;
|
|
11
|
+
export declare function isFunctionHigherOrder(id: NodeId, graph: DataflowGraph, ctx: ReadOnlyFlowrAnalyzerContext, invertedGraph?: DataflowGraph): boolean;
|
|
@@ -32,8 +32,8 @@ function isAnyReturnAFunction(def, graph) {
|
|
|
32
32
|
}
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
35
|
-
function inspectCallSitesArgumentsFns(def, graph, ctx) {
|
|
36
|
-
const callSites = graph.ingoingEdges(def.id);
|
|
35
|
+
function inspectCallSitesArgumentsFns(def, graph, ctx, invertedGraph) {
|
|
36
|
+
const callSites = invertedGraph?.outgoingEdges(def.id) ?? graph.ingoingEdges(def.id);
|
|
37
37
|
for (const [callerId, e] of callSites ?? []) {
|
|
38
38
|
if (!edge_1.DfEdge.includesType(e, edge_1.EdgeType.Calls)) {
|
|
39
39
|
continue;
|
|
@@ -59,8 +59,9 @@ function inspectCallSitesArgumentsFns(def, graph, ctx) {
|
|
|
59
59
|
* either takes a function as an argument or (may) returns a function.
|
|
60
60
|
* If the return is an identity, e.g., `function(x) x`, this is not considered higher-order,
|
|
61
61
|
* if no function is passed as an argument.
|
|
62
|
+
* Please note that inspecting higher order functions can be sped up (if queries multiple times) by providing an inverted graph as well!
|
|
62
63
|
*/
|
|
63
|
-
function isFunctionHigherOrder(id, graph, ctx) {
|
|
64
|
+
function isFunctionHigherOrder(id, graph, ctx, invertedGraph) {
|
|
64
65
|
const vert = graph.getVertex(id);
|
|
65
66
|
if (!vert || !(0, vertex_1.isFunctionDefinitionVertex)(vert)) {
|
|
66
67
|
return false;
|
|
@@ -70,6 +71,6 @@ function isFunctionHigherOrder(id, graph, ctx) {
|
|
|
70
71
|
return true;
|
|
71
72
|
}
|
|
72
73
|
// 2. check whether any of the callsites passes a function
|
|
73
|
-
return inspectCallSitesArgumentsFns(vert, graph, ctx);
|
|
74
|
+
return inspectCallSitesArgumentsFns(vert, graph, ctx, invertedGraph);
|
|
74
75
|
}
|
|
75
76
|
//# sourceMappingURL=higher-order-function.js.map
|
package/dataflow/graph/graph.js
CHANGED
|
@@ -169,8 +169,9 @@ class DataflowGraph {
|
|
|
169
169
|
ingoingEdges(id) {
|
|
170
170
|
const edges = new Map();
|
|
171
171
|
for (const [source, outgoing] of this.edgeInformation.entries()) {
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
const o = outgoing.get(id);
|
|
173
|
+
if (o) {
|
|
174
|
+
edges.set(source, o);
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
return edges;
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ exports.executeHigherOrderQuery = executeHigherOrderQuery;
|
|
|
4
4
|
const higher_order_function_1 = require("../../../dataflow/fn/higher-order-function");
|
|
5
5
|
const parse_1 = require("../../../slicing/criterion/parse");
|
|
6
6
|
const vertex_1 = require("../../../dataflow/graph/vertex");
|
|
7
|
+
const invert_dfg_1 = require("../../../dataflow/graph/invert-dfg");
|
|
7
8
|
/**
|
|
8
9
|
* Execute higher-order function inspection queries on the given analyzer.
|
|
9
10
|
*/
|
|
@@ -34,9 +35,13 @@ async function executeHigherOrderQuery({ analyzer }, queries) {
|
|
|
34
35
|
const graph = (await analyzer.dataflow()).graph;
|
|
35
36
|
const fns = graph.verticesOfType(vertex_1.VertexType.FunctionDefinition)
|
|
36
37
|
.filter(([, v]) => filterFor.size === 0 || filterFor.has(v.id));
|
|
38
|
+
let invertedGraph;
|
|
39
|
+
if (filterFor.size === 0 || filterFor.size > 10) {
|
|
40
|
+
invertedGraph = (0, invert_dfg_1.invertDfg)(graph, analyzer.inspectContext().env.makeCleanEnv());
|
|
41
|
+
}
|
|
37
42
|
const result = {};
|
|
38
43
|
for (const [id] of fns) {
|
|
39
|
-
result[id] = (0, higher_order_function_1.isFunctionHigherOrder)(id, graph, analyzer.inspectContext());
|
|
44
|
+
result[id] = (0, higher_order_function_1.isFunctionHigherOrder)(id, graph, analyzer.inspectContext(), invertedGraph);
|
|
40
45
|
}
|
|
41
46
|
return {
|
|
42
47
|
'.meta': {
|
package/util/version.js
CHANGED
|
@@ -6,7 +6,7 @@ exports.printVersionInformation = printVersionInformation;
|
|
|
6
6
|
const semver_1 = require("semver");
|
|
7
7
|
const assert_1 = require("./assert");
|
|
8
8
|
// this is automatically replaced with the current version by release-it
|
|
9
|
-
const version = '2.9.
|
|
9
|
+
const version = '2.9.5';
|
|
10
10
|
/**
|
|
11
11
|
* Retrieves the current flowR version as a new {@link SemVer} object.
|
|
12
12
|
*/
|