@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
232 lines • 10.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processLoadCall = processLoadCall;
const known_call_handling_1 = require("../known-call-handling");
const identifier_1 = require("../../../../../environments/identifier");
const flowr_rda_file_1 = require("../../../../../../project/plugins/file-plugins/files/flowr-rda-file");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const retriever_1 = require("../../../../../../r-bridge/retriever");
const built_in_source_1 = require("./built-in-source");
const vertex_1 = require("../../../../../graph/vertex");
const logger_1 = require("../../../../../logger");
const unknown_side_effect_1 = require("../../../../../graph/unknown-side-effect");
const edge_1 = require("../../../../../graph/edge");
const unpack_argument_1 = require("../argument/unpack-argument");
const node_value_1 = require("../../../../../eval/resolve/node-value");
const r_value_1 = require("../../../../../eval/values/r-value");
const assert_1 = require("../../../../../../util/assert");
const range_1 = require("../../../../../../util/range");
const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name");
const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const built_in_envir_utils_1 = require("./built-in-envir-utils");
/**
* Processes a built-in 'load' function call by retrieving the names of the variables loaded by the given file.
* Example: `load(test.rda)` with two variables 'x' and 'y'. processLoadCall adds 'x' and 'y' to the dataflow graph and
* adds control dependencies between the variables and the loaded file.
*/
function processLoadCall(name, args, rootId, data) {
const { fileArg, envirArg } = getArguments(args, data);
if (!fileArg) {
const fn = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: 'default' });
(0, unknown_side_effect_1.handleUnknownSideEffect)(fn.information.graph, fn.information.environment, rootId);
return fn.information;
}
const fn = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: built_in_proc_name_1.BuiltInProcName.Load });
if (data.ctx.config.ignoreLoadCalls) {
logger_1.dataflowLogger.warn(`Skipping load call ${JSON.stringify(fileArg)} (disabled in config file)`);
(0, unknown_side_effect_1.handleUnknownSideEffect)(fn.information.graph, fn.information.environment, rootId);
return fn.information;
}
const envirResolution = envirArg ? (0, built_in_envir_utils_1.resolveArgToEnvir)(envirArg, data) : undefined;
if (envirResolution) {
fn.information.graph.addEdge(rootId, envirResolution.envirNodeId, edge_1.EdgeType.Reads);
}
let sourceFile;
if (fileArg.type === type_1.RType.String) {
sourceFile = [(0, retriever_1.removeRQuotes)(fileArg.lexeme)];
}
else {
const resolved = node_value_1.NodeValue.setOf(fileArg.info.id, data, { environment: envirResolution?.envirData.environment ?? data.environment });
sourceFile = resolved?.elements.map(r => r.type === 'string' && (0, r_value_1.isValue)(r.value) ? r.value.str : undefined).filter(assert_1.isNotUndefined);
}
if (sourceFile) {
for (const candidate of sourceFile) {
const path = (0, retriever_1.removeRQuotes)(candidate);
let filepath = path ? (0, built_in_source_1.findSource)(data.ctx.config.solver.resolveSource, path, data) : path;
if (Array.isArray(filepath)) {
if (filepath.length > 1) {
logger_1.dataflowLogger.warn(`Found multiple candidate files for load(${JSON.stringify(path)}): ${JSON.stringify(filepath)}, using first match`);
}
filepath = filepath.find(assert_1.isNotUndefined);
}
if (filepath === undefined) {
continue;
}
let variables;
try {
const file = data.ctx.files.resolveFile(filepath);
variables = file instanceof flowr_rda_file_1.FlowrRDAFile ? file.content() : null;
}
catch (e) {
logger_1.dataflowLogger.warn(`Failed to parse RDA file ${JSON.stringify(filepath)}: ${String(e)}`);
continue;
}
if (variables === null) {
logger_1.dataflowLogger.warn(`Could not read ${JSON.stringify(filepath)} as an RDA file, treating the load as unknown`);
continue;
}
if (variables.length === 0) {
return fn.information;
}
let envir = envirResolution ? envirResolution.envirData.environment : fn.information.environment;
const loadLocation = name.location ?? name.fullRange ?? range_1.SourceRange.invalid();
const loadCds = [...(data.cds ?? []), { id: rootId, when: true, file: filepath }];
for (const variable of variables) {
if (variable.name) {
envir = defineLoadedVariable({ ...variable, name: variable.name }, rootId, fn, envir, loadLocation, loadCds, data);
}
}
return { ...fn.information, environment: envir };
}
}
(0, unknown_side_effect_1.handleUnknownSideEffect)(fn.information.graph, fn.information.environment, rootId);
return fn.information;
}
function defineLoadedVariable(variable, rootId, fn, envir, loadLocation, loadCds, data) {
const syntheticId = `${rootId}:loaded:${variable.name}`;
const isClosure = variable.type === flowr_rda_file_1.SexpType.CloSxp;
const rootInfo = data.completeAst.idMap.get(rootId)?.info;
data.completeAst.idMap.set(syntheticId, {
type: type_1.RType.Symbol,
content: variable.name,
lexeme: variable.name,
location: loadLocation,
namespace: undefined,
info: {
...rootInfo,
id: syntheticId,
parent: rootId,
role: "el-c" /* RoleInParent.ExpressionListChild */,
}
});
if (isClosure) {
defineLoadedClosure(syntheticId, variable, fn, envir, loadLocation, loadCds, data, rootInfo);
}
else {
fn.information.graph.addVertex({
tag: vertex_1.VertexType.VariableDefinition,
id: syntheticId,
cds: loadCds,
}, envir);
}
const nodeToDefine = {
nodeId: syntheticId,
name: variable.name,
type: sexpTypeToReferenceType(variable.type),
definedAt: rootId,
cds: loadCds,
};
const newCurrent = envir.current.define(nodeToDefine);
return { ...envir, current: newCurrent };
}
function defineLoadedClosure(syntheticId, variable, fn, envir, loadLocation, loadCds, data, rootInfo) {
const fdefId = `${syntheticId}:fdef`;
const cleanEnv = data.ctx.env.makeCleanEnv();
const bodyId = `${fdefId}:body`;
const body = {
type: type_1.RType.ExpressionList,
lexeme: undefined,
grouping: undefined,
children: [],
location: loadLocation,
info: {
...rootInfo,
id: bodyId,
parent: fdefId,
role: "fun-b" /* RoleInParent.FunctionDefinitionBody */,
}
};
data.completeAst.idMap.set(bodyId, body);
data.completeAst.idMap.set(fdefId, {
type: type_1.RType.FunctionDefinition,
parameters: [],
lexeme: variable.name,
location: loadLocation,
body,
info: {
...rootInfo,
id: fdefId,
parent: syntheticId,
role: "el-c" /* RoleInParent.ExpressionListChild */,
}
});
const flow = {
entryPoint: fdefId,
graph: new Set(),
out: [],
in: [],
unknownReferences: [],
hooks: [],
environment: cleanEnv
};
fn.information.graph.addVertex({
tag: vertex_1.VertexType.FunctionDefinition,
id: fdefId,
cds: loadCds,
environment: cleanEnv,
subflow: flow,
exitPoints: [],
params: {},
}, cleanEnv);
fn.information.graph.addVertex({
tag: vertex_1.VertexType.VariableDefinition,
id: syntheticId,
cds: loadCds,
}, envir);
fn.information.graph.addEdge(syntheticId, fdefId, edge_1.EdgeType.DefinedBy);
}
function sexpTypeToReferenceType(type) {
if (type === undefined) {
return identifier_1.ReferenceType.Unknown;
}
switch (type) {
case flowr_rda_file_1.SexpType.NilSxp:
return identifier_1.ReferenceType.Unknown;
case flowr_rda_file_1.SexpType.SymSxp:
case flowr_rda_file_1.SexpType.CharSxp:
case flowr_rda_file_1.SexpType.LglSxp:
case flowr_rda_file_1.SexpType.IntSxp:
case flowr_rda_file_1.SexpType.RealSxp:
case flowr_rda_file_1.SexpType.CplxSxp:
case flowr_rda_file_1.SexpType.StrSxp:
case flowr_rda_file_1.SexpType.RawSxp:
case flowr_rda_file_1.SexpType.ListSxp:
case flowr_rda_file_1.SexpType.EnvSxp:
case flowr_rda_file_1.SexpType.PromSxp:
case flowr_rda_file_1.SexpType.LangSxp:
case flowr_rda_file_1.SexpType.DotSxp:
case flowr_rda_file_1.SexpType.VecSxp:
case flowr_rda_file_1.SexpType.ExprSxp:
case flowr_rda_file_1.SexpType.ObjSxp:
return identifier_1.ReferenceType.Variable;
case flowr_rda_file_1.SexpType.CloSxp:
return identifier_1.ReferenceType.Function;
case flowr_rda_file_1.SexpType.SpecialSxp:
case flowr_rda_file_1.SexpType.BuiltInSxp:
return identifier_1.ReferenceType.BuiltInFunction;
default:
return identifier_1.ReferenceType.Unknown;
}
}
function getArguments(args, data) {
// prefer R's real `base::load` signature from the database, falling back to the known formals when it is absent
const loadParams = (0, built_in_envir_utils_1.signatureParamNames)(data, identifier_1.Identifier.make('load', "base" /* PkgName.Base */), ['file', 'envir', 'verbose']);
const bound = r_function_call_1.RFunctionCall.matchArgsToParams(args, loadParams);
const fileArgBound = bound.get('file');
const envirArg = bound.get('envir');
const verboseArgBound = bound.get('verbose');
const fileArg = fileArgBound ? (0, unpack_argument_1.unpackArg)(fileArgBound) : undefined;
const verboseArg = verboseArgBound ? (0, unpack_argument_1.unpackArg)(verboseArgBound) : undefined;
return { fileArg, envirArg, verboseArg };
}
//# sourceMappingURL=built-in-load.js.map