@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
215 lines • 9.61 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processAccess = processAccess;
exports.symbolArgumentsToStrings = symbolArgumentsToStrings;
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 type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const edge_1 = require("../../../../../graph/edge");
const environment_1 = require("../../../../../environments/environment");
const built_in_1 = require("../../../../../environments/built-in");
const built_in_assignment_1 = require("./built-in-assignment");
const identifier_1 = require("../../../../../environments/identifier");
const vertex_1 = require("../../../../../graph/vertex");
const containers_1 = require("../../../../../../util/containers");
const config_1 = require("../../../../../../config");
function tableAssignmentProcessor(name, args, rootId, data, outInfo) {
outInfo.definitionRootNodes.push(rootId);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data }).information;
}
/**
* Processes different types of access operations.
*
* Example:
* ```r
* a[i]
* a$foo
* a[[i]]
* a@foo
* ```
*/
function processAccess(name, args, rootId, data, config) {
if (args.length < 2) {
logger_1.dataflowLogger.warn(`Access ${name.content} has less than 2 arguments, skipping`);
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs }).information;
}
const head = args[0];
let fnCall;
if (head === r_function_call_1.EmptyArgument) {
// in this case we may be within a pipe
fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs });
}
else if (!config.treatIndicesAsString) {
/* within an access operation which treats its fields, we redefine the table assignment ':=' as a trigger if this is to be treated as a definition */
// do we have a local definition that needs to be recovered?
fnCall = processNumberBasedAccess(data, name, args, rootId, config, head);
}
else {
fnCall = processStringBasedAccess(args, data, name, rootId, config);
}
const info = fnCall.information;
if (head !== r_function_call_1.EmptyArgument) {
info.graph.addEdge(name.info.id, fnCall.processedArguments[0]?.entryPoint ?? head.info.id, edge_1.EdgeType.Returns);
}
/* access always reads all of its indices */
for (const arg of fnCall.processedArguments) {
if (arg) {
info.graph.addEdge(name.info.id, arg.entryPoint, edge_1.EdgeType.Reads);
}
/* we include the read edges to the constant arguments as well so that they are included if necessary */
}
return {
...info,
/*
* Keep active nodes in case of assignments etc.
* We make them maybe as a kind of hack.
* This way when using
* ```ts
* a[[1]] <- 3
* a[[2]] <- 4
* a
* ```
* the read for a will use both accesses as potential definitions and not just the last one!
*/
unknownReferences: (0, environment_1.makeAllMaybe)(info.unknownReferences, info.graph, info.environment, false),
entryPoint: rootId,
/** it is, to be precise, the accessed element we want to map to maybe */
in: head === r_function_call_1.EmptyArgument ? info.in : info.in.map(ref => {
if (ref.nodeId === head.value?.info.id) {
return (0, environment_1.makeReferenceMaybe)(ref, info.graph, info.environment, false);
}
else {
return ref;
}
})
};
}
/**
* Processes different types of number-based access operations.
*
* Example:
* ```r
* a[i]
* a[[i]]
* ```
*/
function processNumberBasedAccess(data, name, args, rootId, config, head) {
const existing = data.environment.current.memory.get(':=');
const outInfo = { definitionRootNodes: [] };
data.environment.current.memory.set(':=', [{
type: identifier_1.ReferenceType.BuiltInFunction,
definedAt: built_in_1.BuiltIn,
controlDependencies: undefined,
processor: (name, args, rootId, data) => tableAssignmentProcessor(name, args, rootId, data, outInfo),
config: {},
name: ':=',
nodeId: built_in_1.BuiltIn,
}]);
const fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: config.forceArgs });
/* recover the environment */
if (existing !== undefined) {
data.environment.current.memory.set(':=', existing);
}
if (head.value && outInfo.definitionRootNodes.length > 0) {
(0, built_in_assignment_1.markAsAssignment)(fnCall.information, { type: identifier_1.ReferenceType.Variable, name: head.value.lexeme ?? '', nodeId: head.value.info.id, definedAt: rootId, controlDependencies: [] }, outInfo.definitionRootNodes, rootId);
}
if ((0, config_1.getConfig)().solver.pointerTracking) {
referenceAccessedIndices(args, data, fnCall, rootId, true);
}
return fnCall;
}
function symbolArgumentsToStrings(args, firstIndexInclusive = 1, lastIndexInclusive = args.length - 1) {
const newArgs = [...args];
// if the argument is a symbol, we convert it to a string for this perspective
for (let i = firstIndexInclusive; i <= lastIndexInclusive; i++) {
const arg = newArgs[i];
if (arg !== r_function_call_1.EmptyArgument && arg.value?.type === type_1.RType.Symbol) {
newArgs[i] = {
...arg,
value: {
type: type_1.RType.String,
info: arg.value.info,
lexeme: arg.value.lexeme,
location: arg.value.location,
content: {
quotes: 'none',
str: arg.value.lexeme
}
}
};
}
}
return newArgs;
}
/**
* Processes different types of string-based access operations.
*
* Example:
* ```r
* a$foo
* a@foo
* ```
*/
function processStringBasedAccess(args, data, name, rootId, config) {
const newArgs = symbolArgumentsToStrings(args);
const fnCall = (0, known_call_handling_1.processKnownFunctionCall)({ name, args: newArgs, rootId, data, forceArgs: config.forceArgs });
if ((0, config_1.getConfig)().solver.pointerTracking) {
referenceAccessedIndices(newArgs, data, fnCall, rootId, false);
}
return fnCall;
}
function referenceAccessedIndices(newArgs, data, fnCall, rootId, isIndexBasedAccess) {
// Resolve access on the way up the fold
const { accessedArg, accessArg } = (0, containers_1.getAccessOperands)(newArgs);
if (accessedArg === undefined || accessArg === undefined) {
return;
}
let accessedIndicesCollection;
// If the accessedArg is a symbol, it's either a simple access or the base case of a nested access
if (accessedArg.value?.type === type_1.RType.Symbol) {
accessedIndicesCollection = (0, containers_1.resolveSingleIndex)(accessedArg, accessArg, data.environment, isIndexBasedAccess);
}
else {
// Higher access call
const underlyingAccessId = accessedArg.value?.info.id ?? -1;
const vertex = fnCall.information.graph.getVertex(underlyingAccessId);
const subIndices = vertex?.indicesCollection
?.flatMap(indices => indices.indices)
?.flatMap(index => index?.subIndices ?? []);
if (subIndices) {
accessedIndicesCollection = (0, containers_1.filterIndices)(subIndices, accessArg, isIndexBasedAccess);
}
}
// Add indices to vertex afterward
if (accessedIndicesCollection) {
const vertex = fnCall.information.graph.getVertex(rootId);
if (vertex) {
vertex.indicesCollection = accessedIndicesCollection;
}
// When access has no access as parent, it's the top most
const rootNode = data.completeAst.idMap.get(rootId);
const parentNode = data.completeAst.idMap.get(rootNode?.info.parent ?? -1);
if (parentNode?.type !== type_1.RType.Access) {
// Only reference indices in top most access
referenceIndices(accessedIndicesCollection, fnCall, rootId);
}
}
}
/**
* Creates edges of type {@link EdgeType.Reads} to the accessed Indices and their sub-indices starting from
* the node with {@link parentNodeId}.
*
* @param accessedIndicesCollection - All indices that were accessed by the access operation
* @param fnCall - The {@link ProcessKnownFunctionCallResult} of the access operation
* @param parentNodeId - {@link NodeId} of the parent from which the edge starts
*/
function referenceIndices(accessedIndicesCollection, fnCall, parentNodeId) {
const accessedIndices = accessedIndicesCollection?.flatMap(indices => indices.indices);
for (const accessedIndex of accessedIndices ?? []) {
fnCall.information.graph.addEdge(parentNodeId, accessedIndex.nodeId, edge_1.EdgeType.Reads);
const accessedSubIndices = (0, vertex_1.isParentContainerIndex)(accessedIndex) ? accessedIndex.subIndices : undefined;
referenceIndices(accessedSubIndices, fnCall, accessedIndex.nodeId);
}
}
//# sourceMappingURL=built-in-access.js.map