@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
204 lines • 8.62 kB
JavaScript
;
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