@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
212 lines • 10.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Enrichments = exports.Enrichment = void 0;
exports.enrichmentContent = enrichmentContent;
exports.enrichElement = enrichElement;
const objects_1 = require("../../util/objects");
const vertex_1 = require("../../dataflow/graph/vertex");
const assert_1 = require("../../util/assert");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
const cfg_simplification_1 = require("../../control-flow/cfg-simplification");
const call_context_query_executor_1 = require("../../queries/catalog/call-context-query/call-context-query-executor");
const cfg_kind_1 = require("../../project/cfg-kind");
const identify_link_to_last_call_relation_1 = require("../../queries/catalog/call-context-query/identify-link-to-last-call-relation");
const identifier_1 = require("../../dataflow/environments/identifier");
const df_helper_1 = require("../../dataflow/graph/df-helper");
const model_1 = require("../../r-bridge/lang-4.x/ast/model/model");
/**
* An enumeration that stores the names of the available enrichments that can be applied to a set of search elements.
* See {@link FlowrSearchBuilder.with} for more information on how to apply enrichments.
*/
var Enrichment;
(function (Enrichment) {
Enrichment["CallTargets"] = "call-targets";
Enrichment["LastCall"] = "last-call";
Enrichment["CfgInformation"] = "cfg-information";
Enrichment["Roxygen"] = "roxygen";
Enrichment["QueryData"] = "query-data";
})(Enrichment || (exports.Enrichment = Enrichment = {}));
/**
* The registry of enrichments that are currently supported by the search.
* See {@link FlowrSearchBuilder.with} for more information on how to apply enrichments.
*/
exports.Enrichments = {
[Enrichment.CallTargets]: {
enrichSearch: async (_search, data, _args, prev) => prev ?? {
dfg: await data.dataflow(),
ast: await data.normalize()
},
enrichElement: async (e, s, analyzer, args, prev) => {
// we don't resolve aliases here yet!
const content = { targets: [] };
const shared = s.enrichmentContent(Enrichment.CallTargets);
const df = shared?.dfg ?? await analyzer.dataflow();
const n = shared?.ast ?? await analyzer.normalize();
const callVertex = df.graph.getVertex(e.node.info.id);
if (vertex_1.FunctionCallVertex.is(callVertex)) {
const origins = df_helper_1.Dataflow.origin(df.graph, callVertex.id);
if (!origins || origins.length === 0) {
const name = (0, node_id_1.recoverName)(callVertex.id, n.idMap);
// we don't have origin information here, so pass undefined
content.targets = [qualifyIdentifier(undefined, name)];
}
else {
// find call targets in user code (which have ids!)
content.targets = origins.map(o => {
switch (o.type) {
case 2 /* OriginType.FunctionCallOrigin */: {
if (node_id_1.NodeId.isBuiltIn(o.id)) {
// a built-in target (e.g. a materialized package export from `library()`) has no
// user-code node, so surface it as a built-in identifier (see `onlyBuiltin` below)
const name = (0, node_id_1.recoverName)(o.id, n.idMap);
return qualifyIdentifier([o], name) ?? String(o.id);
}
else {
return { node: n.idMap.get(o.id) };
}
}
case 3 /* OriginType.BuiltInFunctionOrigin */:
return qualifyIdentifier([o], o.fn.name);
default:
return undefined;
}
}).filter(assert_1.isNotUndefined);
if (content.targets.length === 0) {
const name = (0, node_id_1.recoverName)(callVertex.id, n.idMap);
content.targets = [qualifyIdentifier(origins, name)];
}
}
}
// keep only calls whose targets are all built-in; library/package exports arrive as an identifier
// targets and count as built-in, a target with a `node` is user code and disqualifies the call
if (args?.onlyBuiltin && content.targets.some(t => typeof t !== 'string')) {
content.targets = [];
}
if (prev) {
content.targets.push(...prev.targets);
}
return content;
function qualifyIdentifier(origins, name) {
if (args?.qualifyNames === undefined || args.qualifyNames) {
const qualified = identifier_1.Identifier.toQualified(origins, name);
if (qualified !== undefined) {
return identifier_1.Identifier.toString(qualified);
}
}
return name;
}
},
// as built-in call target enrichments are not nodes, we don't return them as part of the mapper!
mapper: ({ targets }) => targets.map(t => t).filter(t => t.node !== undefined)
},
[Enrichment.LastCall]: {
enrichSearch: async (_search, data, _args, prev) => prev ?? {
dfg: await data.dataflow(),
ast: await data.normalize(),
cfg: await data.controlflow(undefined, cfg_kind_1.CfgKind.Quick)
},
enrichElement: async (e, s, analyzer, args, prev) => {
(0, assert_1.guard)(args && args.length, `${Enrichment.LastCall} enrichment requires at least one argument`);
const content = prev ?? { linkedIds: [] };
const shared = s.enrichmentContent(Enrichment.LastCall);
const df = (shared?.dfg ?? await analyzer.dataflow()).graph;
const vertex = df.getVertex(e.node.info.id);
if (vertex_1.FunctionCallVertex.is(vertex)) {
const n = shared?.ast ?? await analyzer.normalize();
const cfg = (shared?.cfg ?? await analyzer.controlflow(undefined, cfg_kind_1.CfgKind.Quick)).graph;
for (const arg of args) {
const lastCalls = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelationSync)(vertex.id, cfg, df, {
...arg,
callName: (0, call_context_query_executor_1.promoteCallName)(arg.callName),
type: 'link-to-last-call',
});
for (const lastCall of lastCalls) {
content.linkedIds.push({ node: n.idMap.get(lastCall) });
}
}
}
return content;
},
mapper: ({ linkedIds }) => linkedIds
},
[Enrichment.CfgInformation]: {
enrichElement: (e, search, _data, _args, prev) => {
const searchContent = search.enrichmentContent(Enrichment.CfgInformation);
return {
...prev,
isRoot: searchContent.cfg.graph.rootIds().has(e.node.info.id),
isReachable: searchContent.reachableNodes?.has(e.node.info.id)
};
},
enrichSearch: async (_search, data, args, prev) => {
args = {
forceRefresh: false,
checkReachable: false,
simplificationPasses: cfg_simplification_1.DefaultCfgSimplificationOrder,
...args
};
// short-circuit if we already have a cfg stored (and the reachability info if requested)
if (!args.forceRefresh && prev?.cfg && (!args.checkReachable || prev.reachableNodes)) {
return prev;
}
const content = {
...prev,
cfg: await data.controlflow(args.simplificationPasses, cfg_kind_1.CfgKind.WithDataflow),
};
if (args.checkReachable) {
content.reachableNodes = (0, cfg_simplification_1.cfgFindAllReachable)(content.cfg);
}
return content;
}
},
[Enrichment.Roxygen]: {
enrichSearch: async (_search, data, _args, prev) => prev ?? { ast: await data.normalize() },
enrichElement: async (e, search, analyzer, _args, prev) => {
const content = (prev ?? {
documentation: [],
tags: {}
});
const shared = search.enrichmentContent(Enrichment.Roxygen);
const normalize = shared?.ast ?? await analyzer.normalize();
const roxygen = model_1.RNode.documentation(e.node.info.id, normalize.idMap);
if (roxygen !== undefined) {
const comments = (Array.isArray(roxygen) ? roxygen : [roxygen]);
content.documentation.push(...comments);
for (const comment of comments) {
content.tags[comment.type] ??= [];
content.tags[comment.type].push(comment);
}
}
return content;
}
},
[Enrichment.QueryData]: {
// the query data enrichment is just a "pass-through" that passes the query data to the underlying search
enrichElement: (_e, _search, _data, args, prev) => (args ?? prev),
enrichSearch: (_search, _data, args, prev) => (0, objects_1.deepMergeObject)(prev, args)
}
};
/**
* Returns the content of the given enrichment type from a {@link FlowrSearchElement}.
* If the search element is not enriched with the given enrichment, `undefined` is returned.
* @param e - The search element whose enrichment content should be retrieved.
* @param enrichment - The enrichment content, if present, else `undefined`.
*/
function enrichmentContent(e, enrichment) {
return e?.enrichments?.[enrichment];
}
/**
* Enriches the given search element with the given enrichment type, using the provided analysis data.
*/
async function enrichElement(e, s, analyzer, enrichment, args) {
const enrichmentData = exports.Enrichments[enrichment];
const prev = e?.enrichments;
return {
...e,
enrichments: {
...prev ?? {},
[enrichment]: await enrichmentData.enrichElement?.(e, s, analyzer, args, prev?.[enrichment])
}
};
}
//# sourceMappingURL=search-enrichers.js.map