UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

181 lines 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processors = void 0; exports.produceDataFlowGraph = produceDataFlowGraph; const processor_1 = require("./processor"); const process_uninteresting_leaf_1 = require("./internal/process/process-uninteresting-leaf"); const process_symbol_1 = require("./internal/process/process-symbol"); const default_call_handling_1 = require("./internal/process/functions/call/default-call-handling"); const process_parameter_1 = require("./internal/process/functions/process-parameter"); const process_argument_1 = require("./internal/process/functions/process-argument"); const process_named_call_1 = require("./internal/process/process-named-call"); const process_value_1 = require("./internal/process/process-value"); const named_call_handling_1 = require("./internal/process/functions/call/named-call-handling"); const make_argument_1 = require("./internal/process/functions/call/argument/make-argument"); const type_1 = require("../r-bridge/lang-4.x/ast/model/type"); const built_in_source_1 = require("./internal/process/functions/call/built-in/built-in-source"); const built_in_library_1 = require("./internal/process/functions/call/built-in/built-in-library"); const graph_1 = require("./graph/graph"); const extract_cfg_1 = require("../control-flow/extract-cfg"); const edge_1 = require("./graph/edge"); const identify_link_to_last_call_relation_1 = require("../queries/catalog/call-context-query/identify-link-to-last-call-relation"); const built_in_function_definition_1 = require("./internal/process/functions/call/built-in/built-in-function-definition"); const transitive_side_effects_1 = require("./internal/process/functions/call/built-in/transitive-side-effects"); const flowr_file_1 = require("../project/context/flowr-file"); /** Round cap for the transitive-side-effect fixpoint. */ const transitiveSideEffectRounds = 32; const identifier_1 = require("./environments/identifier"); const quoted_1 = require("./internal/process/functions/call/quoted"); const range_1 = require("../util/range"); const logger_1 = require("./logger"); const gas_1 = require("../gas"); const df_helper_1 = require("./graph/df-helper"); /** * The best friend of {@link produceDataFlowGraph} and {@link processDataflowFor}. * Maps every {@link RType} in the normalized AST to a processor. */ exports.processors = { [type_1.RType.Number]: process_value_1.processValue, [type_1.RType.String]: process_value_1.processValue, [type_1.RType.Logical]: process_value_1.processValue, [type_1.RType.Comment]: process_uninteresting_leaf_1.processUninterestingLeaf, [type_1.RType.LineDirective]: process_uninteresting_leaf_1.processUninterestingLeaf, [type_1.RType.Symbol]: process_symbol_1.processSymbol, [type_1.RType.Access]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.operator, [n.accessed, ...n.access]), [type_1.RType.BinaryOp]: process_named_call_1.processChainedCall, [type_1.RType.Pipe]: process_named_call_1.processChainedCall, [type_1.RType.UnaryOp]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.operator, [n.operand]), [type_1.RType.ForLoop]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.lexeme, [n.variable, n.vector, n.body]), [type_1.RType.WhileLoop]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.lexeme, [n.condition, n.body]), [type_1.RType.RepeatLoop]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.lexeme, [n.body]), [type_1.RType.IfThenElse]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.lexeme, [n.condition, n.then, n.otherwise]), [type_1.RType.Break]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.lexeme, []), [type_1.RType.Next]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.lexeme, []), [type_1.RType.FunctionCall]: default_call_handling_1.processFunctionCall, [type_1.RType.FunctionDefinition]: (n, d) => (0, process_named_call_1.processAsNamedCall)(n, d, n.lexeme, [...n.parameters, n.body]), [type_1.RType.Parameter]: process_parameter_1.processFunctionParameter, [type_1.RType.Argument]: process_argument_1.processFunctionArgument, [type_1.RType.ExpressionList]: ({ grouping, info, children, location }, d) => { const groupStart = grouping?.[0]; return (0, named_call_handling_1.processNamedCall)({ type: type_1.RType.Symbol, info: info, content: groupStart?.content ?? '{', lexeme: groupStart?.lexeme ?? '{', location: location ?? range_1.SourceRange.invalid(), ns: groupStart?.content ? undefined : 'base' }, (0, make_argument_1.wrapArgumentsUnnamed)(children, d.completeAst.idMap), info.id, d); } }; function resolveLinkToSideEffects(ast, graph, ctx) { const gasLevel = ctx.gas.checkGas(gas_1.GasFeatureKey.SideEffectLinking); if (gasLevel >= 2 /* GasLevel.Critical */) { logger_1.dataflowLogger.warn('Skipping side-effect link resolution due to resource pressure (gas: critical). See ' + gas_1.GasWikiRef); return undefined; } else if (gasLevel >= 1 /* GasLevel.Problematic */) { logger_1.dataflowLogger.warn('Approaching resource limits during side-effect link resolution (gas: problematic). See ' + gas_1.GasWikiRef); } let cf = undefined; let knownCalls; let allCallNames = []; const killedRegexes = new Set(); const handled = new Set(); for (const s of graph.unknownSideEffects) { if (!graph_1.UnknownSideEffect.isLinked(s)) { continue; } if (cf === undefined) { cf = (0, extract_cfg_1.extractCfgQuick)(ast); if (graph.unknownSideEffects.size > 20) { knownCalls = (0, extract_cfg_1.getCallsInCfg)(cf, graph); allCallNames = Array.from(new Set(knownCalls.values().map(c => identifier_1.Identifier.toString(c.name)))); } } else if (handled.has(s.id)) { continue; } handled.add(s.id); const regexKey = s.linkTo.callName.source + '//' + s.linkTo.callName.flags; if (killedRegexes.has(regexKey)) { // we already know we will not find it! continue; } else if (allCallNames.length > 0 && !allCallNames.some(name => s.linkTo.callName.test(name))) { // we know no call matches the regex killedRegexes.add(regexKey); continue; } /* this has to change whenever we add a new link to relations because we currently offer no abstraction for the type */ const potentials = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelationSync)(s.id, cf.graph, graph, s.linkTo, knownCalls); for (const pot of potentials) { graph.addEdge(s.id, pot, edge_1.EdgeType.Reads); } if (potentials.length > 0) { graph.unknownSideEffects.delete(s); } } return cf; } /** * This is the main function to produce the dataflow graph from a given request and normalized AST. * Note, that this requires knowledge of the active parser in case the dataflow analysis uncovers other files that have to be parsed and integrated into the analysis * (e.g., in the event of a `source` call). * For the actual, canonical fold entry point, see {@link processDataflowFor}. */ function produceDataFlowGraph(parser, completeAst, ctx) { // we freeze the files here to avoid endless modifications during processing const files = completeAst.ast.files.slice(); ctx.files.addConsideredFile(files[0].filePath ? files[0].filePath : flowr_file_1.FlowrFile.INLINE_PATH); const env = ctx.env.makeCleanEnv(); env.current.n = ctx.meta.getNamespace(); const environment = (0, built_in_library_1.attachProject)(env, ctx); const dfData = { parser, completeAst, environment, processors: ctx.config.solver.instrument.dataflowExtractors?.(exports.processors, ctx) ?? exports.processors, cds: undefined, referenceChain: [files[0].filePath], ctx }; let df; try { df = (0, processor_1.processDataflowFor)(files[0].root, dfData); } catch (e) { if (e instanceof RangeError) { throw new Error(`Dataflow analysis exceeded the call stack for '${files[0].filePath ?? '<inline>'}' (code is too deeply nested). Consider --stack-size=65536 when invoking Node.js.`, { cause: e }); } throw e; } for (let i = 1; i < files.length; i++) { /* source requests register automatically */ df = (0, built_in_source_1.standaloneSourceFile)(i, files[i], dfData, df); } // resolve linkages and propagate transitive side effects across calls to a fixpoint (0, built_in_function_definition_1.updateNestedFunctionCalls)(df.graph, df.environment, ctx); const escapedNames = new Set(); for (let round = 0; round < transitiveSideEffectRounds; round++) { const { environment, grew, escapedNames: roundNames } = df_helper_1.Dataflow.sideEffects.propagateTransitive(df.graph, df.environment, ctx); df.environment = environment; for (const n of roundNames) { escapedNames.add(n); } if (!grew) { break; } (0, built_in_function_definition_1.updateNestedFunctionCalls)(df.graph, df.environment, ctx); } // resolve top-level reads that now see the escaped `<<-` definitions folded in above if (escapedNames.size > 0) { (0, transitive_side_effects_1.reResolveOpenReferences)(df.graph, df.environment, [...df.in, ...df.unknownReferences], escapedNames); } // link on-demand-materialized package exports back to their `library()` loaders (0, transitive_side_effects_1.linkMaterializedExportsToLoaders)(df.graph, df.environment); quoted_1.Quoted.finalize(df.graph, completeAst.idMap, () => (0, extract_cfg_1.extractCfgQuick)(completeAst).graph); df.cfgQuick = resolveLinkToSideEffects(completeAst, df.graph, ctx); // performance optimization: return cfgQuick as part of the result to avoid recomputation return df; } //# sourceMappingURL=extractor.js.map