@eagleoutice/flowr 2.1.8 → 2.1.9
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/benchmark/summarizer/first-phase/process.js +6 -5
- package/cli/repl/commands/repl-dataflow.js +5 -2
- package/cli/repl/commands/repl-normalize.js +5 -2
- package/cli/repl/commands/repl-query.js +2 -2
- package/cli/repl/server/messages/message-query.js +1 -1
- package/dataflow/environments/default-builtin-config.js +45 -6
- package/dataflow/environments/environment.d.ts +46 -8
- package/dataflow/environments/environment.js +24 -1
- package/dataflow/environments/identifier.d.ts +49 -7
- package/dataflow/environments/identifier.js +11 -2
- package/dataflow/extractor.js +5 -4
- package/dataflow/graph/dataflowgraph-builder.d.ts +6 -0
- package/dataflow/graph/dataflowgraph-builder.js +8 -0
- package/dataflow/graph/edge.d.ts +10 -4
- package/dataflow/graph/edge.js +12 -5
- package/dataflow/graph/graph.d.ts +41 -3
- package/dataflow/graph/graph.js +39 -34
- package/dataflow/graph/vertex.d.ts +66 -7
- package/dataflow/graph/vertex.js +15 -0
- package/dataflow/info.d.ts +79 -11
- package/dataflow/info.js +20 -0
- package/dataflow/internal/linker.d.ts +4 -2
- package/dataflow/internal/linker.js +12 -5
- package/dataflow/internal/process/functions/call/built-in/built-in-assignment.d.ts +2 -0
- package/dataflow/internal/process/functions/call/built-in/built-in-assignment.js +5 -3
- package/dataflow/internal/process/functions/call/built-in/built-in-function-definition.d.ts +16 -0
- package/dataflow/internal/process/functions/call/built-in/built-in-function-definition.js +83 -6
- package/dataflow/internal/process/functions/call/common.js +1 -1
- package/documentation/doc-util/doc-dfg.d.ts +0 -1
- package/documentation/doc-util/doc-dfg.js +1 -14
- package/documentation/print-capabilities-markdown.js +1 -1
- package/documentation/print-dataflow-graph-wiki.js +26 -7
- package/documentation/print-linting-and-testing-wiki.js +60 -26
- package/documentation/print-query-wiki.js +1 -1
- package/package.json +17 -3
- package/queries/catalog/call-context-query/call-context-query-executor.js +1 -1
- package/queries/catalog/call-context-query/call-context-query-format.d.ts +13 -0
- package/queries/catalog/call-context-query/call-context-query-format.js +3 -1
- package/queries/catalog/call-context-query/cascade-action.d.ts +8 -0
- package/queries/catalog/call-context-query/cascade-action.js +13 -0
- package/queries/catalog/call-context-query/identify-link-to-last-call-relation.d.ts +11 -1
- package/queries/catalog/call-context-query/identify-link-to-last-call-relation.js +41 -4
- package/queries/catalog/dependencies-query/dependencies-query-format.js +4 -0
- package/queries/query.d.ts +4 -4
- package/queries/query.js +17 -5
- package/r-bridge/lang-4.x/ast/model/model.d.ts +3 -0
- package/r-bridge/lang-4.x/ast/model/nodes/r-number.d.ts +5 -1
- package/r-bridge/lang-4.x/ast/model/processing/node-id.d.ts +6 -1
- package/r-bridge/lang-4.x/ast/model/processing/node-id.js +6 -1
- package/slicing/static/slice-call.d.ts +7 -2
- package/slicing/static/slice-call.js +33 -44
- package/slicing/static/static-slicer.d.ts +5 -1
- package/slicing/static/static-slicer.js +22 -8
- package/slicing/static/visiting-queue.d.ts +4 -4
- package/slicing/static/visiting-queue.js +5 -3
- package/statistics/output/print-stats.js +2 -1
- package/statistics/summarizer/post-process/histogram.js +2 -1
- package/statistics/summarizer/post-process/post-process-output.js +2 -1
- package/statistics/summarizer/second-phase/process.js +3 -3
- package/util/arrays.d.ts +1 -1
- package/util/arrays.js +3 -3
- package/util/cfg/cfg.js +4 -2
- package/util/mermaid/cfg.js +1 -1
- package/util/summarizer.js +2 -2
- package/util/version.js +1 -1
|
@@ -6,6 +6,9 @@ import Joi from 'joi';
|
|
|
6
6
|
import type { PipelineOutput } from '../../../core/steps/pipeline/pipeline';
|
|
7
7
|
import type { DEFAULT_DATAFLOW_PIPELINE } from '../../../core/steps/pipeline/default-pipelines';
|
|
8
8
|
import { CallTargets } from './identify-link-to-last-call-relation';
|
|
9
|
+
import type { DataflowGraph } from '../../../dataflow/graph/graph';
|
|
10
|
+
import type { DataflowGraphVertexInfo } from '../../../dataflow/graph/vertex';
|
|
11
|
+
import type { CascadeAction } from './cascade-action';
|
|
9
12
|
export interface FileFilter<FilterType> {
|
|
10
13
|
/**
|
|
11
14
|
* Regex that a node's file attribute must match to be considered
|
|
@@ -52,6 +55,16 @@ interface LinkToLastCall<CallName extends RegExp | string = RegExp | string> ext
|
|
|
52
55
|
readonly type: 'link-to-last-call';
|
|
53
56
|
/** Regex regarding the function name of the last call. Similar to {@link DefaultCallContextQueryFormat#callName}, strings are interpreted as a `RegExp`. */
|
|
54
57
|
readonly callName: CallName;
|
|
58
|
+
/**
|
|
59
|
+
* Should we ignore this (source) call?
|
|
60
|
+
* Currently, there is no well working serialization for this.
|
|
61
|
+
*/
|
|
62
|
+
readonly ignoreIf?: (id: NodeId, graph: DataflowGraph) => boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Should we continue searching after the link was created?
|
|
65
|
+
* Currently, there is no well working serialization for this.
|
|
66
|
+
*/
|
|
67
|
+
readonly cascadeIf?: (target: DataflowGraphVertexInfo, from: NodeId, graph: DataflowGraph) => CascadeAction;
|
|
55
68
|
}
|
|
56
69
|
export type LinkTo<CallName extends RegExp | string> = LinkToLastCall<CallName>;
|
|
57
70
|
export interface SubCallContextQueryFormat<CallName extends RegExp | string = RegExp | string> extends DefaultCallContextQueryFormat<CallName> {
|
|
@@ -32,7 +32,9 @@ exports.CallContextQueryDefinition = {
|
|
|
32
32
|
}).optional().description('Filter that, when set, a node\'s file attribute must match to be considered'),
|
|
33
33
|
linkTo: joi_1.default.object({
|
|
34
34
|
type: joi_1.default.string().valid('link-to-last-call').required().description('The type of the linkTo sub-query.'),
|
|
35
|
-
callName: joi_1.default.string().required().description('Regex regarding the function name of the last call. Similar to `callName`, strings are interpreted as a regular expression.')
|
|
35
|
+
callName: joi_1.default.string().required().description('Regex regarding the function name of the last call. Similar to `callName`, strings are interpreted as a regular expression.'),
|
|
36
|
+
ignoreIf: joi_1.default.function().optional().description('Should we ignore this (source) call? Currently, there is no well working serialization for this.'),
|
|
37
|
+
cascadeIf: joi_1.default.function().optional().description('Should we continue searching after the link was created? Currently, there is no well working serialization for this.')
|
|
36
38
|
}).optional().description('Links the current call to the last call of the given kind. This way, you can link a call like `points` to the latest graphics plot etc.')
|
|
37
39
|
}).description('Call context query used to find calls in the dataflow graph')
|
|
38
40
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CascadeAction = void 0;
|
|
4
|
+
var CascadeAction;
|
|
5
|
+
(function (CascadeAction) {
|
|
6
|
+
/** The action is to start the cascade */
|
|
7
|
+
CascadeAction["Stop"] = "stop";
|
|
8
|
+
/** The action is to continue the cascade */
|
|
9
|
+
CascadeAction["Continue"] = "continue";
|
|
10
|
+
/** The action is to skip the current node */
|
|
11
|
+
CascadeAction["Skip"] = "skip";
|
|
12
|
+
})(CascadeAction || (exports.CascadeAction = CascadeAction = {}));
|
|
13
|
+
//# sourceMappingURL=cascade-action.js.map
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
|
|
2
2
|
import type { ControlFlowGraph } from '../../../util/cfg/cfg';
|
|
3
3
|
import type { DataflowGraph } from '../../../dataflow/graph/graph';
|
|
4
|
+
import type { DataflowGraphVertexFunctionCall } from '../../../dataflow/graph/vertex';
|
|
5
|
+
import { RType } from '../../../r-bridge/lang-4.x/ast/model/type';
|
|
6
|
+
import type { RNodeWithParent } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate';
|
|
7
|
+
import type { LinkTo } from './call-context-query-format';
|
|
4
8
|
export declare enum CallTargets {
|
|
5
9
|
/** call targets a function that is not defined locally (e.g., the call targets a library function) */
|
|
6
10
|
OnlyGlobal = "global",
|
|
@@ -14,4 +18,10 @@ export declare enum CallTargets {
|
|
|
14
18
|
Any = "any"
|
|
15
19
|
}
|
|
16
20
|
export declare function satisfiesCallTargets(id: NodeId, graph: DataflowGraph, callTarget: CallTargets): NodeId[] | 'no';
|
|
17
|
-
export declare function
|
|
21
|
+
export declare function getValueOfArgument<Types extends readonly RType[] = readonly RType[]>(graph: DataflowGraph, call: DataflowGraphVertexFunctionCall | undefined, argument: {
|
|
22
|
+
name?: string;
|
|
23
|
+
index: number;
|
|
24
|
+
}, additionalAllowedTypes?: Types): (RNodeWithParent & {
|
|
25
|
+
type: Types[number];
|
|
26
|
+
}) | undefined;
|
|
27
|
+
export declare function identifyLinkToLastCallRelation(from: NodeId, cfg: ControlFlowGraph, graph: DataflowGraph, { callName, ignoreIf, cascadeIf }: LinkTo<RegExp>): NodeId[];
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CallTargets = void 0;
|
|
4
4
|
exports.satisfiesCallTargets = satisfiesCallTargets;
|
|
5
|
+
exports.getValueOfArgument = getValueOfArgument;
|
|
5
6
|
exports.identifyLinkToLastCallRelation = identifyLinkToLastCallRelation;
|
|
7
|
+
const graph_1 = require("../../../dataflow/graph/graph");
|
|
6
8
|
const visitor_1 = require("../../../util/cfg/visitor");
|
|
7
9
|
const vertex_1 = require("../../../dataflow/graph/vertex");
|
|
8
10
|
const edge_1 = require("../../../dataflow/graph/edge");
|
|
@@ -10,6 +12,9 @@ const resolve_by_name_1 = require("../../../dataflow/environments/resolve-by-nam
|
|
|
10
12
|
const identifier_1 = require("../../../dataflow/environments/identifier");
|
|
11
13
|
const built_in_1 = require("../../../dataflow/environments/built-in");
|
|
12
14
|
const assert_1 = require("../../../util/assert");
|
|
15
|
+
const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type");
|
|
16
|
+
const r_function_call_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
|
|
17
|
+
const cascade_action_1 = require("./cascade-action");
|
|
13
18
|
var CallTargets;
|
|
14
19
|
(function (CallTargets) {
|
|
15
20
|
/** call targets a function that is not defined locally (e.g., the call targets a library function) */
|
|
@@ -74,8 +79,36 @@ function satisfiesCallTargets(id, graph, callTarget) {
|
|
|
74
79
|
(0, assert_1.assertUnreachable)(callTarget);
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
|
-
function
|
|
82
|
+
function getValueOfArgument(graph, call, argument, additionalAllowedTypes) {
|
|
83
|
+
if (!call) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const totalIndex = argument.name ? call.args.findIndex(arg => arg !== r_function_call_1.EmptyArgument && arg.name === argument.name) : -1;
|
|
87
|
+
let refAtIndex;
|
|
88
|
+
if (totalIndex < 0) {
|
|
89
|
+
const references = call.args.filter(arg => arg !== r_function_call_1.EmptyArgument && !arg.name).map(graph_1.getReferenceOfArgument);
|
|
90
|
+
refAtIndex = references[argument.index];
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const arg = call.args[totalIndex];
|
|
94
|
+
refAtIndex = (0, graph_1.getReferenceOfArgument)(arg);
|
|
95
|
+
}
|
|
96
|
+
if (refAtIndex === undefined) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
let valueNode = graph.idMap?.get(refAtIndex);
|
|
100
|
+
if (valueNode?.type === type_1.RType.Argument) {
|
|
101
|
+
valueNode = valueNode.value;
|
|
102
|
+
}
|
|
103
|
+
if (valueNode) {
|
|
104
|
+
return !additionalAllowedTypes || additionalAllowedTypes.includes(valueNode.type) ? valueNode : undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function identifyLinkToLastCallRelation(from, cfg, graph, { callName, ignoreIf, cascadeIf }) {
|
|
78
108
|
const found = [];
|
|
109
|
+
if (ignoreIf && ignoreIf(from, graph)) {
|
|
110
|
+
return found;
|
|
111
|
+
}
|
|
79
112
|
(0, visitor_1.visitInReverseOrder)(cfg, from, node => {
|
|
80
113
|
/* we ignore the start id as it cannot be the last call */
|
|
81
114
|
if (node === from) {
|
|
@@ -85,13 +118,17 @@ function identifyLinkToLastCallRelation(from, cfg, graph, linkTo) {
|
|
|
85
118
|
if (vertex === undefined || vertex[0].tag !== vertex_1.VertexType.FunctionCall) {
|
|
86
119
|
return;
|
|
87
120
|
}
|
|
88
|
-
if (
|
|
121
|
+
if (callName.test(vertex[0].name)) {
|
|
122
|
+
const act = cascadeIf ? cascadeIf(vertex[0], from, graph) : cascade_action_1.CascadeAction.Stop;
|
|
123
|
+
if (act === cascade_action_1.CascadeAction.Skip) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
89
126
|
const tar = satisfiesCallTargets(vertex[0].id, graph, CallTargets.MustIncludeGlobal);
|
|
90
127
|
if (tar === 'no') {
|
|
91
|
-
return
|
|
128
|
+
return act === cascade_action_1.CascadeAction.Stop;
|
|
92
129
|
}
|
|
93
130
|
found.push(node);
|
|
94
|
-
return
|
|
131
|
+
return act === cascade_action_1.CascadeAction.Stop;
|
|
95
132
|
}
|
|
96
133
|
});
|
|
97
134
|
return found;
|
|
@@ -83,6 +83,8 @@ exports.ReadFunctions = [
|
|
|
83
83
|
{ name: 'read.ssd', argIdx: 0, argName: 'file' },
|
|
84
84
|
{ name: 'read.systat', argIdx: 0, argName: 'file' },
|
|
85
85
|
{ name: 'read.xport', argIdx: 0, argName: 'file' },
|
|
86
|
+
// car
|
|
87
|
+
{ name: 'Import', argIdx: 0, argName: 'file' },
|
|
86
88
|
];
|
|
87
89
|
exports.WriteFunctions = [
|
|
88
90
|
{ name: 'save', argIdx: 0, argName: '...' },
|
|
@@ -138,6 +140,8 @@ exports.WriteFunctions = [
|
|
|
138
140
|
{ name: 'tiff', argIdx: 0, argName: 'file' },
|
|
139
141
|
{ name: 'X11', argIdx: 0, argName: 'file' },
|
|
140
142
|
{ name: 'quartz', argIdx: 0, argName: 'file' },
|
|
143
|
+
// car
|
|
144
|
+
{ name: 'Export', argIdx: 0, argName: 'file' },
|
|
141
145
|
];
|
|
142
146
|
function printResultSection(title, infos, result, sectionSpecifics) {
|
|
143
147
|
if (infos.length <= 0) {
|
package/queries/query.d.ts
CHANGED
|
@@ -450,9 +450,9 @@ type OmitFromValues<T, K extends string | number | symbol> = {
|
|
|
450
450
|
export type QueryResultsWithoutMeta<Queries extends Query> = OmitFromValues<Omit<QueryResults<Queries['type']>, '.meta'>, '.meta'>;
|
|
451
451
|
export type Queries<Base extends SupportedQueryTypes, VirtualArguments extends VirtualCompoundConstraint<Base> = VirtualCompoundConstraint<Base>> = readonly (QueryArgumentsWithType<Base> | VirtualQueryArgumentsWithType<Base, VirtualArguments>)[];
|
|
452
452
|
export declare function executeQueries<Base extends SupportedQueryTypes, VirtualArguments extends VirtualCompoundConstraint<Base> = VirtualCompoundConstraint<Base>>(data: BasicQueryData, queries: Queries<Base, VirtualArguments>): QueryResults<Base>;
|
|
453
|
-
export declare
|
|
453
|
+
export declare function SupportedQueriesSchema(): Joi.AlternativesSchema<any>;
|
|
454
454
|
export declare const CompoundQuerySchema: Joi.ObjectSchema<any>;
|
|
455
|
-
export declare
|
|
456
|
-
export declare
|
|
457
|
-
export declare
|
|
455
|
+
export declare function VirtualQuerySchema(): Joi.AlternativesSchema<any>;
|
|
456
|
+
export declare function AnyQuerySchema(): Joi.AlternativesSchema<any>;
|
|
457
|
+
export declare function QueriesSchema(): Joi.ArraySchema<any[]>;
|
|
458
458
|
export {};
|
package/queries/query.js
CHANGED
|
@@ -3,9 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.CompoundQuerySchema = exports.SupportedQueries = void 0;
|
|
7
7
|
exports.executeQueriesOfSameType = executeQueriesOfSameType;
|
|
8
8
|
exports.executeQueries = executeQueries;
|
|
9
|
+
exports.SupportedQueriesSchema = SupportedQueriesSchema;
|
|
10
|
+
exports.VirtualQuerySchema = VirtualQuerySchema;
|
|
11
|
+
exports.AnyQuerySchema = AnyQuerySchema;
|
|
12
|
+
exports.QueriesSchema = QueriesSchema;
|
|
9
13
|
const call_context_query_format_1 = require("./catalog/call-context-query/call-context-query-format");
|
|
10
14
|
const assert_1 = require("../util/assert");
|
|
11
15
|
const virtual_queries_1 = require("./virtual-query/virtual-queries");
|
|
@@ -74,14 +78,22 @@ function executeQueries(data, queries) {
|
|
|
74
78
|
};
|
|
75
79
|
return results;
|
|
76
80
|
}
|
|
77
|
-
|
|
81
|
+
function SupportedQueriesSchema() {
|
|
82
|
+
return joi_1.default.alternatives(Object.values(exports.SupportedQueries).map(q => q.schema)).description('Supported queries');
|
|
83
|
+
}
|
|
78
84
|
exports.CompoundQuerySchema = joi_1.default.object({
|
|
79
85
|
type: joi_1.default.string().valid('compound').required().description('The type of the query.'),
|
|
80
86
|
query: joi_1.default.string().required().description('The query to run on the file analysis information.'),
|
|
81
87
|
commonArguments: joi_1.default.object().required().description('Common arguments for all queries.'),
|
|
82
88
|
arguments: joi_1.default.array().items(joi_1.default.object()).required().description('Arguments for each query.')
|
|
83
89
|
}).description('Compound query used to combine queries of the same type');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
function VirtualQuerySchema() {
|
|
91
|
+
return joi_1.default.alternatives(exports.CompoundQuerySchema).description('Virtual queries (used for structure)');
|
|
92
|
+
}
|
|
93
|
+
function AnyQuerySchema() {
|
|
94
|
+
return joi_1.default.alternatives(SupportedQueriesSchema(), VirtualQuerySchema()).description('Any query');
|
|
95
|
+
}
|
|
96
|
+
function QueriesSchema() {
|
|
97
|
+
return joi_1.default.array().items(AnyQuerySchema()).description('Queries to run on the file analysis information (in the form of an array)');
|
|
98
|
+
}
|
|
87
99
|
//# sourceMappingURL=query.js.map
|
|
@@ -145,6 +145,9 @@ export type ROther<Info> = RComment<Info> | RLineDirective<Info>;
|
|
|
145
145
|
* All other subtypes (like {@link RLoopConstructs}) listed above
|
|
146
146
|
* can be used to restrict the kind of node. They do not have to be
|
|
147
147
|
* exclusive, some nodes can appear in multiple subtypes.
|
|
148
|
+
*
|
|
149
|
+
* @see {@link recoverName} - to receive the name/lexeme from such a node
|
|
150
|
+
* @see {@link recoverContent} - for a more rigorous approach to get the content of a node within a {@link DataflowGraph|dataflow graph}
|
|
148
151
|
*/
|
|
149
152
|
export type RNode<Info = NoInfo> = RExpressionList<Info> | RFunctions<Info> | ROther<Info> | RConstructs<Info> | RNamedAccess<Info> | RIndexAccess<Info> | RUnaryOp<Info> | RBinaryOp<Info> | RSingleNode<Info> | RPipe<Info>;
|
|
150
153
|
export type OtherInfoNode = RNode | RDelimiter;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { Leaf, Location, NoInfo } from '../model';
|
|
2
2
|
import type { RType } from '../type';
|
|
3
3
|
import type { RNumberValue } from '../../../convert-values';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A number like `3`, `-2.14`, `1L`, or `2i`.
|
|
6
|
+
* Includes numeric, integer, and complex.
|
|
7
|
+
* See {@link RNumberValue} for more information.
|
|
8
|
+
*/
|
|
5
9
|
export interface RNumber<Info = NoInfo> extends Leaf<Info>, Location {
|
|
6
10
|
readonly type: RType.Number;
|
|
7
11
|
content: RNumberValue;
|
|
@@ -7,7 +7,12 @@ export type NodeId<T extends string | number = string | number> = T & {
|
|
|
7
7
|
/** used so that we do not have to store strings for the default numeric ids */
|
|
8
8
|
export declare function normalizeIdToNumberIfPossible(id: NodeId): NodeId;
|
|
9
9
|
/**
|
|
10
|
-
* Recovers the lexeme of a node from its id in the
|
|
10
|
+
* Recovers the lexeme of a {@link RNode|node} from its id in the {@link AstIdMap|id map}.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link recoverContent} - to recover the content of a node
|
|
11
13
|
*/
|
|
12
14
|
export declare function recoverName(id: NodeId, idMap?: AstIdMap): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Recovers the content of a {@link RNode|node} from its id in the {@link DataflowGraph|dataflow graph}.
|
|
17
|
+
*/
|
|
13
18
|
export declare function recoverContent(id: NodeId, graph: DataflowGraph): string | undefined;
|
|
@@ -15,11 +15,16 @@ function normalizeIdToNumberIfPossible(id) {
|
|
|
15
15
|
return id;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Recovers the lexeme of a node from its id in the
|
|
18
|
+
* Recovers the lexeme of a {@link RNode|node} from its id in the {@link AstIdMap|id map}.
|
|
19
|
+
*
|
|
20
|
+
* @see {@link recoverContent} - to recover the content of a node
|
|
19
21
|
*/
|
|
20
22
|
function recoverName(id, idMap) {
|
|
21
23
|
return idMap?.get(id)?.lexeme;
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Recovers the content of a {@link RNode|node} from its id in the {@link DataflowGraph|dataflow graph}.
|
|
27
|
+
*/
|
|
23
28
|
function recoverContent(id, graph) {
|
|
24
29
|
const vertex = graph.getVertex(id);
|
|
25
30
|
if (vertex === undefined) {
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { NodeToSlice } from './slicer-types';
|
|
2
2
|
import type { VisitingQueue } from './visiting-queue';
|
|
3
3
|
import type { Fingerprint } from './fingerprint';
|
|
4
|
-
import type { DataflowGraphVertexFunctionCall } from '../../dataflow/graph/vertex';
|
|
4
|
+
import type { DataflowGraphVertexFunctionCall, DataflowGraphVertexInfo } from '../../dataflow/graph/vertex';
|
|
5
5
|
import type { REnvironmentInformation } from '../../dataflow/environments/environment';
|
|
6
6
|
import type { DataflowGraph, OutgoingEdges } from '../../dataflow/graph/graph';
|
|
7
|
+
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
|
|
8
|
+
/**
|
|
9
|
+
* Returns the function call targets (definitions) by the given caller
|
|
10
|
+
*/
|
|
11
|
+
export declare function getAllFunctionCallTargets(dataflowGraph: DataflowGraph, callerInfo: DataflowGraphVertexFunctionCall, baseEnvironment: REnvironmentInformation): [Set<DataflowGraphVertexInfo>, REnvironmentInformation];
|
|
7
12
|
/** returns the new threshold hit count */
|
|
8
13
|
export declare function sliceForCall(current: NodeToSlice, callerInfo: DataflowGraphVertexFunctionCall, dataflowGraph: DataflowGraph, queue: VisitingQueue): void;
|
|
9
14
|
/** Returns true if we found at least one return edge */
|
|
10
|
-
export declare function handleReturns(queue: VisitingQueue, currentEdges: OutgoingEdges, baseEnvFingerprint: Fingerprint, baseEnvironment: REnvironmentInformation): boolean;
|
|
15
|
+
export declare function handleReturns(from: NodeId, queue: VisitingQueue, currentEdges: OutgoingEdges, baseEnvFingerprint: Fingerprint, baseEnvironment: REnvironmentInformation): boolean;
|
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAllFunctionCallTargets = getAllFunctionCallTargets;
|
|
3
4
|
exports.sliceForCall = sliceForCall;
|
|
4
5
|
exports.handleReturns = handleReturns;
|
|
5
6
|
const assert_1 = require("../../util/assert");
|
|
6
7
|
const fingerprint_1 = require("./fingerprint");
|
|
7
8
|
const linker_1 = require("../../dataflow/internal/linker");
|
|
8
|
-
const environment_1 = require("../../dataflow/environments/environment");
|
|
9
|
-
const scoping_1 = require("../../dataflow/environments/scoping");
|
|
10
|
-
const overwrite_1 = require("../../dataflow/environments/overwrite");
|
|
11
9
|
const graph_1 = require("../../dataflow/graph/graph");
|
|
12
10
|
const built_in_1 = require("../../dataflow/environments/built-in");
|
|
13
11
|
const resolve_by_name_1 = require("../../dataflow/environments/resolve-by-name");
|
|
14
12
|
const edge_1 = require("../../dataflow/graph/edge");
|
|
15
13
|
const identifier_1 = require("../../dataflow/environments/identifier");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const built_in_function_definition_1 = require("../../dataflow/internal/process/functions/call/built-in/built-in-function-definition");
|
|
15
|
+
const static_slicer_1 = require("./static-slicer");
|
|
16
|
+
/**
|
|
17
|
+
* Returns the function call targets (definitions) by the given caller
|
|
18
|
+
*/
|
|
19
|
+
function getAllFunctionCallTargets(dataflowGraph, callerInfo, baseEnvironment) {
|
|
20
|
+
// bind with call-local environments during slicing
|
|
21
|
+
const outgoingEdges = dataflowGraph.get(callerInfo.id, true);
|
|
22
|
+
(0, assert_1.guard)(outgoingEdges !== undefined, () => `outgoing edges of id: ${callerInfo.id} must be in graph but can not be found, keep in slice to be sure`);
|
|
23
|
+
// lift baseEnv on the same level
|
|
24
|
+
const activeEnvironment = (0, built_in_function_definition_1.retrieveActiveEnvironment)(callerInfo.environment, baseEnvironment);
|
|
25
|
+
const name = callerInfo.name;
|
|
26
|
+
(0, assert_1.guard)(name !== undefined, () => `name of id: ${callerInfo.id} can not be found in id map`);
|
|
27
|
+
const functionCallDefs = (0, resolve_by_name_1.resolveByName)(name, activeEnvironment, identifier_1.ReferenceType.Unknown)?.filter(d => d.definedAt !== built_in_1.BuiltIn)?.map(d => d.nodeId) ?? [];
|
|
28
|
+
for (const [target, outgoingEdge] of outgoingEdges[1].entries()) {
|
|
29
|
+
if ((0, edge_1.edgeIncludesType)(outgoingEdge.types, edge_1.EdgeType.Calls)) {
|
|
30
|
+
functionCallDefs.push(target);
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
|
-
|
|
33
|
+
const functionCallTargets = (0, linker_1.getAllLinkedFunctionDefinitions)(new Set(functionCallDefs), dataflowGraph);
|
|
34
|
+
return [functionCallTargets, activeEnvironment];
|
|
29
35
|
}
|
|
30
36
|
function includeArgumentFunctionCallClosure(arg, baseEnvironment, activeEnvironment, queue, dataflowGraph) {
|
|
31
37
|
const valueRoot = (0, graph_1.getReferenceOfArgument)(arg);
|
|
@@ -33,20 +39,21 @@ function includeArgumentFunctionCallClosure(arg, baseEnvironment, activeEnvironm
|
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
41
|
const callTargets = (0, linker_1.getAllLinkedFunctionDefinitions)(new Set([valueRoot]), dataflowGraph);
|
|
36
|
-
linkCallTargets(false, callTargets,
|
|
42
|
+
linkCallTargets(false, callTargets, activeEnvironment, (0, fingerprint_1.envFingerprint)(activeEnvironment), queue);
|
|
37
43
|
}
|
|
38
|
-
function linkCallTargets(onlyForSideEffects, functionCallTargets,
|
|
44
|
+
function linkCallTargets(onlyForSideEffects, functionCallTargets, activeEnvironment, activeEnvironmentFingerprint, queue) {
|
|
39
45
|
for (const functionCallTarget of functionCallTargets) {
|
|
40
46
|
// all those linked within the scopes of other functions are already linked when exiting a function definition
|
|
41
|
-
for
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
/* for(const openIn of (functionCallTarget as DataflowGraphVertexFunctionDefinition).subflow.in) {
|
|
48
|
+
// only if the outgoing path does not already have a defined by linkage
|
|
49
|
+
const defs = openIn.name ? resolveByName(openIn.name, activeEnvironment, openIn.type) : undefined;
|
|
50
|
+
if(defs === undefined) {
|
|
44
51
|
continue;
|
|
45
52
|
}
|
|
46
|
-
for
|
|
53
|
+
for(const def of defs.filter(d => d.nodeId !== BuiltIn)) {
|
|
47
54
|
queue.add(def.nodeId, baseEnvironment, baseEnvPrint, onlyForSideEffects);
|
|
48
55
|
}
|
|
49
|
-
}
|
|
56
|
+
}*/
|
|
50
57
|
for (const exitPoint of functionCallTarget.exitPoints) {
|
|
51
58
|
queue.add(exitPoint, activeEnvironment, activeEnvironmentFingerprint, onlyForSideEffects);
|
|
52
59
|
}
|
|
@@ -54,23 +61,9 @@ function linkCallTargets(onlyForSideEffects, functionCallTargets, baseEnvironmen
|
|
|
54
61
|
}
|
|
55
62
|
/** returns the new threshold hit count */
|
|
56
63
|
function sliceForCall(current, callerInfo, dataflowGraph, queue) {
|
|
57
|
-
// bind with call-local environments during slicing
|
|
58
|
-
const outgoingEdges = dataflowGraph.get(callerInfo.id, true);
|
|
59
|
-
(0, assert_1.guard)(outgoingEdges !== undefined, () => `outgoing edges of id: ${callerInfo.id} must be in graph but can not be found, keep in slice to be sure`);
|
|
60
|
-
// lift baseEnv on the same level
|
|
61
64
|
const baseEnvironment = current.baseEnvironment;
|
|
62
|
-
const
|
|
63
|
-
const activeEnvironment = retrieveActiveEnvironment(callerInfo, baseEnvironment);
|
|
65
|
+
const [functionCallTargets, activeEnvironment] = getAllFunctionCallTargets(dataflowGraph, callerInfo, current.baseEnvironment);
|
|
64
66
|
const activeEnvironmentFingerprint = (0, fingerprint_1.envFingerprint)(activeEnvironment);
|
|
65
|
-
const name = callerInfo.name;
|
|
66
|
-
(0, assert_1.guard)(name !== undefined, () => `name of id: ${callerInfo.id} can not be found in id map`);
|
|
67
|
-
const functionCallDefs = (0, resolve_by_name_1.resolveByName)(name, activeEnvironment, identifier_1.ReferenceType.Unknown)?.filter(d => d.definedAt !== built_in_1.BuiltIn)?.map(d => d.nodeId) ?? [];
|
|
68
|
-
for (const [target, outgoingEdge] of outgoingEdges[1].entries()) {
|
|
69
|
-
if ((0, edge_1.edgeIncludesType)(outgoingEdge.types, edge_1.EdgeType.Calls)) {
|
|
70
|
-
functionCallDefs.push(target);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
const functionCallTargets = (0, linker_1.getAllLinkedFunctionDefinitions)(new Set(functionCallDefs), dataflowGraph);
|
|
74
67
|
if (functionCallTargets.size === 0) {
|
|
75
68
|
/*
|
|
76
69
|
* if we do not have any call to resolve this function, we have to assume that every function passed is actually called!
|
|
@@ -81,10 +74,10 @@ function sliceForCall(current, callerInfo, dataflowGraph, queue) {
|
|
|
81
74
|
}
|
|
82
75
|
return;
|
|
83
76
|
}
|
|
84
|
-
linkCallTargets(current.onlyForSideEffects, functionCallTargets,
|
|
77
|
+
linkCallTargets(current.onlyForSideEffects, functionCallTargets, activeEnvironment, activeEnvironmentFingerprint, queue);
|
|
85
78
|
}
|
|
86
79
|
/** Returns true if we found at least one return edge */
|
|
87
|
-
function handleReturns(queue, currentEdges, baseEnvFingerprint, baseEnvironment) {
|
|
80
|
+
function handleReturns(from, queue, currentEdges, baseEnvFingerprint, baseEnvironment) {
|
|
88
81
|
const e = [...currentEdges.entries()];
|
|
89
82
|
const found = e.filter(([_, edge]) => (0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.Returns));
|
|
90
83
|
if (found.length === 0) {
|
|
@@ -97,12 +90,8 @@ function handleReturns(queue, currentEdges, baseEnvFingerprint, baseEnvironment)
|
|
|
97
90
|
if ((0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.Reads)) {
|
|
98
91
|
queue.add(target, baseEnvironment, baseEnvFingerprint, false);
|
|
99
92
|
}
|
|
100
|
-
else if ((0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.Argument)) {
|
|
101
|
-
|
|
102
|
-
id: target,
|
|
103
|
-
baseEnvironment,
|
|
104
|
-
onlyForSideEffects: false
|
|
105
|
-
});
|
|
93
|
+
else if ((0, edge_1.edgeIncludesType)(edge.types, edge_1.EdgeType.DefinesOnCall | edge_1.EdgeType.DefinedByOnCall | edge_1.EdgeType.Argument)) {
|
|
94
|
+
(0, static_slicer_1.updatePotentialAddition)(queue, from, target, baseEnvironment);
|
|
106
95
|
}
|
|
107
96
|
}
|
|
108
97
|
return true;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { SliceResult } from './slicer-types';
|
|
2
|
+
import { VisitingQueue } from './visiting-queue';
|
|
2
3
|
import type { DataflowGraph } from '../../dataflow/graph/graph';
|
|
3
4
|
import type { NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
|
|
4
5
|
import type { SlicingCriteria } from '../criterion/parse';
|
|
6
|
+
import type { REnvironmentInformation } from '../../dataflow/environments/environment';
|
|
7
|
+
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
|
|
5
8
|
export declare const slicerLogger: import("tslog").Logger<import("tslog").ILogObj>;
|
|
6
9
|
/**
|
|
7
10
|
* This returns the ids to include in the static backward slice, when slicing with the given seed id's (must be at least one).
|
|
@@ -10,7 +13,8 @@ export declare const slicerLogger: import("tslog").Logger<import("tslog").ILogOb
|
|
|
10
13
|
*
|
|
11
14
|
* @param graph - The dataflow graph to conduct the slicing on.
|
|
12
15
|
* @param ast - The normalized AST of the code (used to get static nesting information of the lexemes in case of control flow dependencies that may have no effect on the slicing scope).
|
|
13
|
-
* @param criteria - The
|
|
16
|
+
* @param criteria - The criterias to slice on.
|
|
14
17
|
* @param threshold - The maximum number of nodes to visit in the graph. If the threshold is reached, the slice will side with inclusion and drop its minimal guarantee. The limit ensures that the algorithm halts.
|
|
15
18
|
*/
|
|
16
19
|
export declare function staticSlicing(graph: DataflowGraph, { idMap }: NormalizedAst, criteria: SlicingCriteria, threshold?: number): Readonly<SliceResult>;
|
|
20
|
+
export declare function updatePotentialAddition(queue: VisitingQueue, id: NodeId, target: NodeId, baseEnvironment: REnvironmentInformation): void;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.slicerLogger = void 0;
|
|
4
4
|
exports.staticSlicing = staticSlicing;
|
|
5
|
+
exports.updatePotentialAddition = updatePotentialAddition;
|
|
5
6
|
const assert_1 = require("../../util/assert");
|
|
6
7
|
const log_1 = require("../../util/log");
|
|
7
8
|
const fingerprint_1 = require("./fingerprint");
|
|
@@ -19,7 +20,7 @@ exports.slicerLogger = log_1.log.getSubLogger({ name: 'slicer' });
|
|
|
19
20
|
*
|
|
20
21
|
* @param graph - The dataflow graph to conduct the slicing on.
|
|
21
22
|
* @param ast - The normalized AST of the code (used to get static nesting information of the lexemes in case of control flow dependencies that may have no effect on the slicing scope).
|
|
22
|
-
* @param criteria - The
|
|
23
|
+
* @param criteria - The criterias to slice on.
|
|
23
24
|
* @param threshold - The maximum number of nodes to visit in the graph. If the threshold is reached, the slice will side with inclusion and drop its minimal guarantee. The limit ensures that the algorithm halts.
|
|
24
25
|
*/
|
|
25
26
|
function staticSlicing(graph, { idMap }, criteria, threshold = 75) {
|
|
@@ -71,7 +72,7 @@ function staticSlicing(graph, { idMap }, criteria, threshold = 75) {
|
|
|
71
72
|
if (currentVertex.tag === vertex_1.VertexType.FunctionCall && !currentVertex.onlyBuiltin) {
|
|
72
73
|
(0, slice_call_1.sliceForCall)(current, currentVertex, graph, queue);
|
|
73
74
|
}
|
|
74
|
-
const ret = (0, slice_call_1.handleReturns)(queue, currentEdges, baseEnvFingerprint, baseEnvironment);
|
|
75
|
+
const ret = (0, slice_call_1.handleReturns)(id, queue, currentEdges, baseEnvFingerprint, baseEnvironment);
|
|
75
76
|
if (ret) {
|
|
76
77
|
continue;
|
|
77
78
|
}
|
|
@@ -84,12 +85,8 @@ function staticSlicing(graph, { idMap }, criteria, threshold = 75) {
|
|
|
84
85
|
if (t === 3 /* TraverseEdge.Always */) {
|
|
85
86
|
queue.add(target, baseEnvironment, baseEnvFingerprint, false);
|
|
86
87
|
}
|
|
87
|
-
else if (t === 2 /* TraverseEdge.
|
|
88
|
-
|
|
89
|
-
if (n) {
|
|
90
|
-
queue.add(target, n.baseEnvironment, (0, fingerprint_1.envFingerprint)(n.baseEnvironment), n.onlyForSideEffects);
|
|
91
|
-
queue.potentialArguments.delete(target);
|
|
92
|
-
}
|
|
88
|
+
else if (t === 2 /* TraverseEdge.OnlyIfBoth */) {
|
|
89
|
+
updatePotentialAddition(queue, id, target, baseEnvironment);
|
|
93
90
|
}
|
|
94
91
|
else if (t === 1 /* TraverseEdge.SideEffect */) {
|
|
95
92
|
queue.add(target, baseEnvironment, baseEnvFingerprint, true);
|
|
@@ -98,4 +95,21 @@ function staticSlicing(graph, { idMap }, criteria, threshold = 75) {
|
|
|
98
95
|
}
|
|
99
96
|
return { ...queue.status(), decodedCriteria };
|
|
100
97
|
}
|
|
98
|
+
function updatePotentialAddition(queue, id, target, baseEnvironment) {
|
|
99
|
+
const n = queue.potentialAdditions.get(target);
|
|
100
|
+
if (n) {
|
|
101
|
+
const [addedBy, { baseEnvironment, onlyForSideEffects }] = n;
|
|
102
|
+
if (addedBy !== id) {
|
|
103
|
+
queue.add(target, baseEnvironment, (0, fingerprint_1.envFingerprint)(baseEnvironment), onlyForSideEffects);
|
|
104
|
+
queue.potentialAdditions.delete(target);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
queue.potentialAdditions.set(target, [id, {
|
|
109
|
+
id: target,
|
|
110
|
+
baseEnvironment,
|
|
111
|
+
onlyForSideEffects: false
|
|
112
|
+
}]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
101
115
|
//# sourceMappingURL=static-slicer.js.map
|
|
@@ -4,10 +4,10 @@ import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-i
|
|
|
4
4
|
export declare class VisitingQueue {
|
|
5
5
|
private readonly threshold;
|
|
6
6
|
private timesHitThreshold;
|
|
7
|
-
private seen;
|
|
8
|
-
private idThreshold;
|
|
9
|
-
private queue;
|
|
10
|
-
|
|
7
|
+
private readonly seen;
|
|
8
|
+
private readonly idThreshold;
|
|
9
|
+
private readonly queue;
|
|
10
|
+
potentialAdditions: Map<NodeId, [NodeId, NodeToSlice]>;
|
|
11
11
|
constructor(threshold: number);
|
|
12
12
|
/**
|
|
13
13
|
* Adds a node to the queue if it has not been seen before.
|
|
@@ -3,14 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.VisitingQueue = void 0;
|
|
4
4
|
const fingerprint_1 = require("./fingerprint");
|
|
5
5
|
const static_slicer_1 = require("./static-slicer");
|
|
6
|
+
const assert_1 = require("../../util/assert");
|
|
6
7
|
class VisitingQueue {
|
|
7
8
|
threshold;
|
|
8
9
|
timesHitThreshold = 0;
|
|
9
10
|
seen = new Map();
|
|
10
11
|
idThreshold = new Map();
|
|
11
12
|
queue = [];
|
|
12
|
-
// the set of potential
|
|
13
|
-
|
|
13
|
+
// the set of potential additions holds nodes which may be added if a second edge deems them relevant (e.g., found with the `defined-by-on-call` edge)
|
|
14
|
+
// additionally it holds which node id added the addition so we can separate their inclusion on the structure
|
|
15
|
+
potentialAdditions = new Map();
|
|
14
16
|
constructor(threshold) {
|
|
15
17
|
this.threshold = threshold;
|
|
16
18
|
}
|
|
@@ -48,7 +50,7 @@ class VisitingQueue {
|
|
|
48
50
|
status() {
|
|
49
51
|
return {
|
|
50
52
|
timesHitThreshold: this.timesHitThreshold,
|
|
51
|
-
result: new Set(this.seen.values())
|
|
53
|
+
result: new Set([...this.seen.values()].filter(assert_1.isNotUndefined))
|
|
52
54
|
};
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -7,9 +7,10 @@ exports.printFeatureStatisticsEntry = printFeatureStatisticsEntry;
|
|
|
7
7
|
const ansi_1 = require("../../util/ansi");
|
|
8
8
|
const json_1 = require("../../util/json");
|
|
9
9
|
const feature_1 = require("../features/feature");
|
|
10
|
+
const arrays_1 = require("../../util/arrays");
|
|
10
11
|
function minMaxAvgAndMedian(data) {
|
|
11
12
|
data = data.sort((a, b) => a - b);
|
|
12
|
-
const sum =
|
|
13
|
+
const sum = (0, arrays_1.arraySum)(data);
|
|
13
14
|
return {
|
|
14
15
|
sum,
|
|
15
16
|
min: data[0],
|
|
@@ -11,6 +11,7 @@ const bimap_1 = require("../../../util/bimap");
|
|
|
11
11
|
const defaultmap_1 = require("../../../util/defaultmap");
|
|
12
12
|
const assert_1 = require("../../../util/assert");
|
|
13
13
|
const summarizer_1 = require("../../../util/summarizer");
|
|
14
|
+
const arrays_1 = require("../../../util/arrays");
|
|
14
15
|
/**
|
|
15
16
|
* Produces column-wise histogram-information based on a {@link ClusterReport}.
|
|
16
17
|
*
|
|
@@ -79,7 +80,7 @@ function histograms2table(histograms, countAsDensity = false) {
|
|
|
79
80
|
(0, assert_1.guard)(histograms.length > 0, 'there must be at least one histogram to convert to a table');
|
|
80
81
|
const mostBins = guardForLargestBinSize(histograms);
|
|
81
82
|
const header = ['bin', 'from', 'to', ...histograms.map(h => JSON.stringify(h.name))];
|
|
82
|
-
const sums = histograms.map(h =>
|
|
83
|
+
const sums = histograms.map(h => (0, arrays_1.arraySum)(h.bins));
|
|
83
84
|
const rows = [];
|
|
84
85
|
for (let binIndex = 0; binIndex < mostBins; binIndex++) {
|
|
85
86
|
const row = new Array(histograms.length + 3);
|