UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

204 lines 8.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processTryCatch = processTryCatch; const info_1 = require("../../../../../info"); const known_call_handling_1 = require("../known-call-handling"); const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const logger_1 = require("../../../../../logger"); const linker_1 = require("../../../../linker"); const vertex_1 = require("../../../../../graph/vertex"); const unpack_argument_1 = require("../argument/unpack-argument"); const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type"); const assert_1 = require("../../../../../../util/assert"); const edge_1 = require("../../../../../graph/edge"); const unnamed_call_handling_1 = require("../unnamed-call-handling"); const identifier_1 = require("../../../../../environments/identifier"); const identifier_2 = require("../../../../../environments/identifier"); const resolve_by_name_1 = require("../../../../../environments/resolve-by-name"); const log_1 = require("../../../../../../util/log"); const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name"); /** * Process a built-in try-catch or similar handler. */ function processTryCatch(name, args, rootId, data, config) { const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: args.map(unpack_argument_1.tryUnpackNoNameArg), rootId, data, origin: built_in_proc_name_1.BuiltInProcName.Try, forceArgs: 'all' }); if (args.length < 1 || args[0] === r_function_call_1.EmptyArgument) { logger_1.dataflowLogger.warn(`TryCatch Handler ${identifier_1.Identifier.toString(name.content)} does not have 1 argument, skipping`); return res.information; } // artificial ids :) const params = { [config.block]: 'block', '...': '...' }; if (config.handlers.error) { params[config.handlers.error] = 'error'; } if (config.handlers.finally) { params[config.handlers.finally] = 'finally'; } // only remove exit points from the block const argMaps = (0, linker_1.pMatch)(res.callArgs, params); const info = res.information; const blockArg = new Set(argMaps.get('block')); const errorArg = new Set(argMaps.get('error')); const finallyArg = new Set(argMaps.get('finally')); // only take those exit points from the block // check whether blockArg has *always* happening exceptions, if so we do not constrain the error handler const blockErrorExitPoints = []; const errorExitPoints = []; info.exitPoints = res.processedArguments.flatMap(arg => { if (!arg) { return []; } // this calls error and finally args if (finallyArg.has(arg.entryPoint)) { return handleFdefAsCalled(arg.entryPoint, info.graph, arg.exitPoints, undefined); } else if (errorArg.has(arg.entryPoint)) { errorExitPoints.push(...getExitPoints(info.graph.getVertex(arg.entryPoint), info.graph) ?? []); } if (!blockArg.has(arg.entryPoint)) { // not killing other args return arg.exitPoints; } blockErrorExitPoints.push(...arg.exitPoints.filter(ep => ep.type === 4 /* ExitPointType.Error */).flatMap(a => a.cds)); return arg.exitPoints.filter(ep => ep.type !== 4 /* ExitPointType.Error */); }); if (errorExitPoints.length > 0) { if ((0, info_1.happensInEveryBranch)(blockErrorExitPoints.some(assert_1.isUndefined) ? undefined : blockErrorExitPoints)) { info.exitPoints.push(...errorExitPoints); } else { info.exitPoints.push(...constrainExitPoints(errorExitPoints, blockArg)); } } for (const e of errorArg) { info.graph.addEdge(rootId, e, edge_1.EdgeType.Reads | edge_1.EdgeType.Calls); const linkTo = promoteCallToFunction(rootId, e, info, data); if (linkTo) { info.graph.addEdge(e, linkTo, edge_1.EdgeType.Calls); } } for (const f of finallyArg) { info.graph.addEdge(rootId, f, edge_1.EdgeType.Calls); } for (const e of info.exitPoints) { if (e.type !== 4 /* ExitPointType.Error */) { info.graph.addEdge(rootId, e.nodeId, edge_1.EdgeType.Returns); } } return info; } function promoteCallToFunction(call, arg, info, data) { let functionId = undefined; let functionName = undefined; let anonymous = false; const argNode = data.completeAst.idMap.get(arg); if (!argNode) { return undefined; } const val = argNode.type === type_1.RType.Argument ? (0, unpack_argument_1.unpackArg)(argNode) : argNode; if (!val) { return undefined; } if (val.type === type_1.RType.Symbol) { functionId = val.info.id; functionName = val.content; } else if (val.type === type_1.RType.FunctionDefinition) { anonymous = true; functionId = val.info.id; functionName = `${unnamed_call_handling_1.UnnamedFunctionCallPrefix}${functionId}`; } if (functionName === undefined || functionId === undefined) { return undefined; } if (anonymous) { info.graph.addEdge(arg, functionId, edge_1.EdgeType.Calls | edge_1.EdgeType.Reads); const dfVert = info.graph.getVertex(call); if (dfVert && dfVert.tag === vertex_1.VertexType.FunctionDefinition) { // resolve all ingoings against the environment const ingoingRefs = dfVert.subflow.in; const remainingIn = []; for (const ingoing of ingoingRefs) { const resolved = ingoing.name ? (0, resolve_by_name_1.resolveByName)(ingoing.name, data.environment, ingoing.type) : undefined; if (resolved === undefined) { remainingIn.push(ingoing); continue; } (0, log_1.expensiveTrace)(logger_1.dataflowLogger, () => `Found ${resolved.length} references to open ref ${ingoing.nodeId} in closure of function definition ${call}`); let allBuiltIn = true; for (const ref of resolved) { const rid = ref.nodeId; info.graph.addEdge(ingoing.nodeId, rid, edge_1.EdgeType.Reads); info.graph.addEdge(call, rid, edge_1.EdgeType.Reads); // because the def. is the anonymous call if (!(0, identifier_2.isReferenceType)(ref.type, identifier_2.ReferenceType.BuiltInConstant | identifier_2.ReferenceType.BuiltInFunction)) { allBuiltIn = false; } } if (allBuiltIn) { remainingIn.push(ingoing); } } dfVert.subflow.in = remainingIn; } // we did the linking return undefined; } else { info.graph.updateToFunctionCall({ tag: vertex_1.VertexType.FunctionCall, id: functionId, name: functionName, args: [], environment: data.environment, onlyBuiltin: false, cds: data.cds, origin: [built_in_proc_name_1.BuiltInProcName.Function] }); return functionId; } } function getExitPoints(vertex, graph) { if (!vertex) { return undefined; } if (vertex.tag === vertex_1.VertexType.FunctionDefinition) { return vertex.exitPoints; } // we assumed named argument const n = graph.idMap?.get(vertex.id); if (!n) { return undefined; } if (n.type === type_1.RType.Argument && n.value?.type === type_1.RType.FunctionDefinition) { const fdefV = graph.getVertex(n.value.info.id); if (fdefV?.tag === vertex_1.VertexType.FunctionDefinition) { return fdefV.exitPoints; } } return undefined; } function handleFdefAsCalled(nodeId, graph, def, constrain) { const v = graph.getVertex(nodeId); const e = getExitPoints(v, graph); return e ? constrainExitPoints(e, constrain) : def; } function constrainExitPoints(exitPoints, constrain) { if (!constrain || constrain.size === 0) { return exitPoints; } // append constrains with true const cds = Array.from(constrain, id => ({ id, when: true })); return exitPoints.map(e => { if (e.cds) { e.cds.push(...cds); return e; } else { return { ...e, cds: cds }; } }); } //# sourceMappingURL=built-in-try-catch.js.map