@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
170 lines • 8.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataFrameShapeInferenceVisitor = void 0;
const control_flow_graph_1 = require("../../control-flow/control-flow-graph");
const semantic_cfg_guided_visitor_1 = require("../../control-flow/semantic-cfg-guided-visitor");
const assert_1 = require("../../util/assert");
const absint_info_1 = require("./absint-info");
const domain_1 = require("./domain");
const access_mapper_1 = require("./mappers/access-mapper");
const assignment_mapper_1 = require("./mappers/assignment-mapper");
const function_mapper_1 = require("./mappers/function-mapper");
const replacement_mapper_1 = require("./mappers/replacement-mapper");
const semantics_1 = require("./semantics");
const shape_inference_1 = require("./shape-inference");
/**
* The control flow graph visitor to infer the shape of data frames using abstract interpretation
*/
class DataFrameShapeInferenceVisitor extends semantic_cfg_guided_visitor_1.SemanticCfgGuidedVisitor {
/**
* The old domain of an AST node before processing the node retrieved from the attached {@link AbstractInterpretationInfo}.
* This is used to check whether the state has changed and successors should be visited again, and is also required for widening.
*/
oldDomain = new Map();
/**
* The new domain of an AST node during and after processing the node.
* This information is stored in the {@link AbstractInterpretationInfo} afterwards.
*/
newDomain = new Map();
constructor(config) {
super({ ...config, defaultVisitingOrder: 'forward', defaultVisitingType: 'exit' });
}
visitNode(nodeId) {
const vertex = this.getCfgVertex(nodeId);
// skip vertices representing entries of complex nodes
if (vertex === undefined || this.shouldSkipVertex(vertex)) {
return true;
}
const predecessors = this.getPredecessorNodes(vertex.id);
this.newDomain = (0, domain_1.joinDataFrameStates)(...predecessors.map(node => node.info.dataFrame?.domain ?? new Map()));
this.onVisitNode(nodeId);
const visitedCount = this.visited.get(vertex.id) ?? 0;
this.visited.set(vertex.id, visitedCount + 1);
// only continue visiting if the node has not been visited before or the data frame value of the node changed
return visitedCount === 0 || !(0, domain_1.equalDataFrameState)(this.oldDomain, this.newDomain);
}
visitDataflowNode(vertex) {
const node = this.getNormalizedAst((0, control_flow_graph_1.getVertexRootId)(vertex));
if (node === undefined) {
return;
}
this.oldDomain = node.info.dataFrame?.domain ?? new Map();
super.visitDataflowNode(vertex);
if (this.shouldWiden(vertex)) {
this.newDomain = (0, domain_1.wideningDataFrameStates)(this.oldDomain, this.newDomain);
}
node.info.dataFrame ??= {};
node.info.dataFrame.domain = this.newDomain;
}
onVariableDefinition({ vertex }) {
const node = this.getNormalizedAst(vertex.id);
if (node !== undefined) {
// mark variable definitions as "unassigned", as the evaluation of the assigned expression is delayed until processing the assignment
node.info.dataFrame ??= { marker: absint_info_1.DataFrameInfoMarker.Unassigned };
}
}
onAssignmentCall({ call, target, source }) {
const node = this.getNormalizedAst(call.id);
const targetNode = this.getNormalizedAst(target);
const sourceNode = this.getNormalizedAst(source);
if (node !== undefined && (0, assignment_mapper_1.isAssignmentTarget)(targetNode) && sourceNode !== undefined) {
node.info.dataFrame = (0, assignment_mapper_1.mapDataFrameVariableAssignment)(targetNode, sourceNode, this.config.dfg);
this.applyDataFrameAssignment(node);
this.clearUnassignedInfo(targetNode);
}
}
onAccessCall({ call }) {
const node = this.getNormalizedAst(call.id);
if (node !== undefined) {
node.info.dataFrame = (0, access_mapper_1.mapDataFrameAccess)(node, this.config.dfg);
this.applyDataFrameExpression(node);
}
}
onDefaultFunctionCall({ call }) {
const node = this.getNormalizedAst(call.id);
if (node !== undefined) {
node.info.dataFrame = (0, function_mapper_1.mapDataFrameFunctionCall)(node, this.config.dfg, this.config.flowrConfig);
this.applyDataFrameExpression(node);
}
}
onReplacementCall({ call, source, target }) {
const node = this.getNormalizedAst(call.id);
const targetNode = this.getNormalizedAst(target);
const sourceNode = this.getNormalizedAst(source);
if (node !== undefined && targetNode !== undefined && sourceNode !== undefined) {
node.info.dataFrame = (0, replacement_mapper_1.mapDataFrameReplacementFunction)(node, sourceNode, this.config.dfg);
this.applyDataFrameExpression(node);
this.clearUnassignedInfo(targetNode);
}
}
applyDataFrameAssignment(node) {
if (!(0, absint_info_1.hasDataFrameAssignmentInfo)(node)) {
return;
}
const value = (0, shape_inference_1.resolveIdToDataFrameShape)(node.info.dataFrame.expression, this.config.dfg, this.newDomain);
if (value !== undefined) {
this.newDomain.set(node.info.dataFrame.identifier, value);
const identifier = this.getNormalizedAst(node.info.dataFrame.identifier);
if (identifier !== undefined) {
identifier.info.dataFrame ??= {};
identifier.info.dataFrame.domain = new Map(this.newDomain);
}
}
}
applyDataFrameExpression(node) {
if (!(0, absint_info_1.hasDataFrameExpressionInfo)(node)) {
return;
}
let value = domain_1.DataFrameTop;
for (const { operation, operand, type, options, ...args } of node.info.dataFrame.operations) {
const operandValue = operand !== undefined ? (0, shape_inference_1.resolveIdToDataFrameShape)(operand, this.config.dfg, this.newDomain) : value;
value = (0, semantics_1.applyDataFrameSemantics)(operation, operandValue ?? domain_1.DataFrameTop, args, options);
const constraintType = type ?? (0, semantics_1.getConstraintType)(operation);
if (operand !== undefined && constraintType === semantics_1.ConstraintType.OperandModification) {
this.newDomain.set(operand, value);
for (const origin of (0, shape_inference_1.getVariableOrigins)(operand, this.config.dfg)) {
this.newDomain.set(origin.info.id, value);
}
}
else if (constraintType === semantics_1.ConstraintType.ResultPostcondition) {
this.newDomain.set(node.info.id, value);
}
}
}
/** We only process vertices of leaf nodes and exit vertices (no entry nodes of complex nodes) */
shouldSkipVertex(vertex) {
return (0, control_flow_graph_1.isMarkerVertex)(vertex) ? vertex.type !== control_flow_graph_1.CfgVertexType.EndMarker : vertex.end !== undefined;
}
/** Get all AST nodes for the predecessor vertices that are leaf nodes and exit vertices */
getPredecessorNodes(vertexId) {
return [...this.config.controlFlow.graph.outgoingEdges(vertexId)?.keys() ?? []] // outgoing dependency edges are incoming CFG edges
.map(id => this.getCfgVertex(id))
.flatMap(vertex => {
if (vertex === undefined) {
return [];
}
else if (this.shouldSkipVertex(vertex)) {
return this.getPredecessorNodes(vertex.id);
}
else {
return [this.getNormalizedAst((0, control_flow_graph_1.getVertexRootId)(vertex))];
}
})
.filter(assert_1.isNotUndefined);
}
shouldWiden(vertex) {
return (this.visited.get(vertex.id) ?? 0) >= this.config.flowrConfig.abstractInterpretation.dataFrame.wideningThreshold;
}
clearUnassignedInfo(node) {
if ((0, absint_info_1.hasDataFrameInfoMarker)(node, absint_info_1.DataFrameInfoMarker.Unassigned)) {
if (node.info.dataFrame?.domain !== undefined) {
node.info.dataFrame = { domain: node.info.dataFrame.domain };
}
else {
delete node.info.dataFrame;
}
}
}
}
exports.DataFrameShapeInferenceVisitor = DataFrameShapeInferenceVisitor;
//# sourceMappingURL=absint-visitor.js.map