UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

170 lines 8.7 kB
"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