@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
302 lines • 14.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.processAssignment = processAssignment;
exports.markAsAssignment = markAsAssignment;
const known_call_handling_1 = require("../known-call-handling");
const log_1 = require("../../../../../../util/log");
const unpack_argument_1 = require("../argument/unpack-argument");
const process_named_call_1 = require("../../../process-named-call");
const make_argument_1 = require("../argument/make-argument");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const logger_1 = require("../../../../../logger");
const identifier_1 = require("../../../../../environments/identifier");
const overwrite_1 = require("../../../../../environments/overwrite");
const retriever_1 = require("../../../../../../r-bridge/retriever");
const vertex_1 = require("../../../../../graph/vertex");
const define_1 = require("../../../../../environments/define");
const edge_1 = require("../../../../../graph/edge");
const resolve_by_name_1 = require("../../../../../environments/resolve-by-name");
const containers_1 = require("../../../../../../util/containers");
const config_1 = require("../../../../../../config");
const named_call_handling_1 = require("../named-call-handling");
const built_in_1 = require("../../../../../environments/built-in");
const unknown_side_effect_1 = require("../../../../../graph/unknown-side-effect");
const alias_tracking_1 = require("../../../../../eval/resolve/alias-tracking");
function toReplacementSymbol(target, prefix, superAssignment) {
return {
type: type_1.RType.Symbol,
info: target.info,
/* they are all mapped to `<-` in R, but we mark super as well */
content: `${prefix}${superAssignment ? '<<-' : '<-'}`,
lexeme: target.lexeme,
location: target.location,
namespace: undefined
};
}
function getEffectiveOrder(config, args) {
return config.swapSourceAndTarget ? [args[1], args[0]] : args;
}
function findRootAccess(node) {
let current = node;
while (current.type === type_1.RType.Access) {
current = current.accessed;
}
if (current.type === type_1.RType.Symbol) {
return current;
}
else {
return undefined;
}
}
function tryReplacementPassingIndices(rootId, functionName, data, name, args, indices) {
const resolved = (0, resolve_by_name_1.resolveByName)(functionName.content, data.environment, identifier_1.ReferenceType.Function) ?? [];
// yield for unsupported pass along!
if (resolved.length !== 1 || resolved[0].type !== identifier_1.ReferenceType.BuiltInFunction) {
return (0, process_named_call_1.processAsNamedCall)(functionName, data, name, args);
}
const info = built_in_1.BuiltInProcessorMapper['builtin:replacement']({
type: type_1.RType.Symbol,
info: functionName.info,
content: name,
lexeme: functionName.lexeme,
location: functionName.location,
namespace: undefined
}, (0, make_argument_1.wrapArgumentsUnnamed)(args, data.completeAst.idMap), functionName.info.id, data, {
...(resolved[0].config ?? {}),
activeIndices: indices,
assignRootId: rootId
});
(0, named_call_handling_1.markAsOnlyBuiltIn)(info.graph, functionName.info.id);
return info;
}
/**
* Processes an assignment, i.e., `<target> <- <source>`.
* Handling it as a function call \`<-\` `(<target>, <source>)`.
* This includes handling of replacement functions (e.g., `names(x) <- ...` as \`names<-\` `(x, ...)`).
*/
function processAssignment(name,
/* we expect them to be ordered in the sense that we have (source, target): `<source> <- <target>` */
args, rootId, data, config) {
if (!config.mayHaveMoreArgs && args.length !== 2) {
logger_1.dataflowLogger.warn(`Assignment ${name.content} has something else than 2 arguments, skipping`);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs, origin: 'default' }).information;
}
const effectiveArgs = getEffectiveOrder(config, args);
const { target, source } = extractSourceAndTarget(effectiveArgs);
if (target === undefined || source === undefined) {
logger_1.dataflowLogger.warn(`Assignment ${name.content} has an undefined target or source, skipping`);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs, origin: 'default' }).information;
}
const { type, named } = target;
if (!config.targetVariable && type === type_1.RType.Symbol) {
const res = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, reverseOrder: !config.swapSourceAndTarget, forceArgs: config.forceArgs, origin: 'builtin:assignment' });
return processAssignmentToSymbol({
...config,
nameOfAssignmentFunction: name.content,
source,
target,
args: getEffectiveOrder(config, res.processedArguments),
rootId,
data,
information: res.information,
});
}
else if (config.canBeReplacement && type === type_1.RType.FunctionCall && named) {
/* as replacement functions take precedence over the lhs fn-call (i.e., `names(x) <- ...` is independent from the definition of `names`), we do not have to process the call */
logger_1.dataflowLogger.debug(`Assignment ${name.content} has a function call as target ==> replacement function ${target.lexeme}`);
const replacement = toReplacementSymbol(target, target.functionName.content, config.superAssignment ?? false);
return tryReplacementPassingIndices(rootId, replacement, data, replacement.content, [...target.arguments, source], config.indicesCollection);
}
else if (config.canBeReplacement && type === type_1.RType.Access) {
logger_1.dataflowLogger.debug(`Assignment ${name.content} has an access-type node as target ==> replacement function ${target.lexeme}`);
const replacement = toReplacementSymbol(target, target.operator, config.superAssignment ?? false);
return tryReplacementPassingIndices(rootId, replacement, data, replacement.content, [(0, make_argument_1.toUnnamedArgument)(target.accessed, data.completeAst.idMap), ...target.access, source], config.indicesCollection);
}
else if (type === type_1.RType.Access) {
const rootArg = findRootAccess(target);
if (rootArg) {
const res = (0, known_call_handling_1.processKnownFunctionCall)({
name,
args: [rootArg, source],
rootId,
data,
reverseOrder: !config.swapSourceAndTarget,
forceArgs: config.forceArgs,
origin: 'builtin:assignment'
});
return processAssignmentToSymbol({
...config,
nameOfAssignmentFunction: name.content,
source,
target: rootArg,
args: getEffectiveOrder(config, res.processedArguments),
rootId,
data,
information: res.information,
});
}
}
else if (type === type_1.RType.String) {
return processAssignmentToString(target, args, name, rootId, data, config, source);
}
logger_1.dataflowLogger.warn(`Assignment ${name.content} has an unknown target type ${target.type} => unknown impact`);
const info = (0, known_call_handling_1.processKnownFunctionCall)({
name, args: effectiveArgs, rootId, data, forceArgs: config.forceArgs,
origin: 'builtin:assignment'
}).information;
(0, unknown_side_effect_1.handleUnknownSideEffect)(info.graph, info.environment, rootId);
return info;
}
function extractSourceAndTarget(args) {
const source = (0, unpack_argument_1.unpackArgument)(args[1], false);
const target = (0, unpack_argument_1.unpackArgument)(args[0], false);
return { source, target };
}
/**
* Promotes the ingoing/unknown references of target (an assignment) to definitions
*/
function produceWrittenNodes(rootId, target, referenceType, data, makeMaybe, value) {
return [...target.in, ...target.unknownReferences].map(ref => ({
...ref,
type: referenceType,
definedAt: rootId,
controlDependencies: data.controlDependencies ?? (makeMaybe ? [] : undefined),
value: value
}));
}
function processAssignmentToString(target, args, name, rootId, data, config, source) {
const symbol = {
type: type_1.RType.Symbol,
info: target.info,
content: (0, retriever_1.removeRQuotes)(target.lexeme),
lexeme: target.lexeme,
location: target.location,
namespace: undefined
};
// treat first argument to Symbol
const mappedArgs = config.swapSourceAndTarget ? [args[0], {
...args[1],
value: symbol
}] : [{ ...args[0], value: symbol }, args[1]];
const res = (0, known_call_handling_1.processKnownFunctionCall)({
name,
args: mappedArgs,
rootId,
data,
reverseOrder: !config.swapSourceAndTarget,
forceArgs: config.forceArgs,
origin: 'builtin:assignment'
});
return processAssignmentToSymbol({
...config,
nameOfAssignmentFunction: name.content,
source,
target: symbol,
args: getEffectiveOrder(config, res.processedArguments),
rootId,
data,
information: res.information
});
}
function checkTargetReferenceType(source, sourceInfo) {
const vert = sourceInfo.graph.getVertex(source.info.id, true);
switch (vert?.tag) {
case vertex_1.VertexType.FunctionDefinition:
return identifier_1.ReferenceType.Function;
case vertex_1.VertexType.Use:
case vertex_1.VertexType.FunctionCall:
return identifier_1.ReferenceType.Unknown;
default:
return identifier_1.ReferenceType.Variable;
}
}
/**
* Consider a call like `x <- v`
* @param information - the information to define the assignment within
* @param nodeToDefine - `x`
* @param sourceIds - `v`
* @param rootIdOfAssignment - `<-`
* @param config - configuration for the assignment processing
*/
function markAsAssignment(information, nodeToDefine, sourceIds, rootIdOfAssignment, config) {
if ((0, config_1.getConfig)().solver.pointerTracking) {
let indicesCollection = undefined;
if (sourceIds.length === 1) {
// support for tracking indices.
// Indices were defined for the vertex e.g. a <- list(c = 1) or a$b <- list(c = 1)
indicesCollection = information.graph.getVertex(sourceIds[0])?.indicesCollection;
// support assignment of container e.g. container1 <- container2
// defined indices are passed
if (!indicesCollection) {
const node = information.graph.idMap?.get(sourceIds[0]);
if (node && node.type === type_1.RType.Symbol) {
indicesCollection = (0, containers_1.resolveIndicesByName)(node.lexeme, information.environment);
}
}
}
// Indices defined by replacement operation e.g. $<-
if (config?.indicesCollection !== undefined) {
// If there were indices stored in the vertex, then a container was defined
// and assigned to the index of another container e.g. a$b <- list(c = 1)
if (indicesCollection) {
indicesCollection = (0, containers_1.addSubIndicesToLeafIndices)(config.indicesCollection, indicesCollection);
}
else {
// No indices were defined for the vertex e.g. a$b <- 2
indicesCollection = config.indicesCollection;
}
}
nodeToDefine.indicesCollection ??= indicesCollection;
}
information.environment = (0, define_1.define)(nodeToDefine, config?.superAssignment, information.environment);
information.graph.setDefinitionOfVertex(nodeToDefine);
if (!config?.quoteSource) {
for (const sourceId of sourceIds) {
information.graph.addEdge(nodeToDefine, sourceId, edge_1.EdgeType.DefinedBy);
}
}
information.graph.addEdge(nodeToDefine, rootIdOfAssignment, edge_1.EdgeType.DefinedBy);
// kinda dirty, but we have to remove existing read edges for the symbol, added by the child
const out = information.graph.outgoingEdges(nodeToDefine.nodeId);
for (const [id, edge] of (out ?? [])) {
edge.types &= ~edge_1.EdgeType.Reads;
if (edge.types === 0) {
out?.delete(id);
}
}
}
/**
* Helper function whenever it is known that the _target_ of an assignment is a (single) symbol (i.e. `x <- ...`, but not `names(x) <- ...`).
*/
function processAssignmentToSymbol(config) {
const { nameOfAssignmentFunction, source, args: [targetArg, sourceArg], target, rootId, data, information, makeMaybe, quoteSource } = config;
const referenceType = checkTargetReferenceType(source, sourceArg);
const aliases = (0, alias_tracking_1.getAliases)([source.info.id], information.graph, information.environment);
const writeNodes = produceWrittenNodes(rootId, targetArg, referenceType, data, makeMaybe ?? false, aliases);
if (writeNodes.length !== 1 && log_1.log.settings.minLevel <= 4 /* LogLevel.Warn */) {
log_1.log.warn(`Unexpected write number in assignment: ${JSON.stringify(writeNodes)}`);
}
// we drop the first arg which we use to pass along arguments :D
const readFromSourceWritten = sourceArg.out.slice(1);
const readTargets = [
{ nodeId: rootId, name: nameOfAssignmentFunction, controlDependencies: data.controlDependencies, type: identifier_1.ReferenceType.Function },
...sourceArg.unknownReferences, ...sourceArg.in, ...targetArg.in.filter(i => i.nodeId !== target.info.id), ...readFromSourceWritten
];
information.environment = (0, overwrite_1.overwriteEnvironment)(targetArg.environment, sourceArg.environment);
// install assigned variables in environment
for (const write of writeNodes) {
markAsAssignment(information, write, [source.info.id], rootId, config);
}
information.graph.addEdge(rootId, targetArg.entryPoint, edge_1.EdgeType.Returns);
if (quoteSource) {
information.graph.addEdge(rootId, source.info.id, edge_1.EdgeType.NonStandardEvaluation);
}
return {
...information,
unknownReferences: [],
entryPoint: rootId,
in: readTargets,
out: [...writeNodes, ...readFromSourceWritten]
};
}
//# sourceMappingURL=built-in-assignment.js.map