UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

124 lines 7.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SEEDED_RANDOMNESS = void 0; const linter_format_1 = require("../linter-format"); const flowr_search_builder_1 = require("../../search/flowr-search-builder"); const dfg_1 = require("../../util/mermaid/dfg"); const search_enrichers_1 = require("../../search/search-executor/search-enrichers"); const flowr_search_filters_1 = require("../../search/flowr-search-filters"); const default_builtin_config_1 = require("../../dataflow/environments/default-builtin-config"); const graph_1 = require("../../dataflow/graph/graph"); const cascade_action_1 = require("../../queries/catalog/call-context-query/cascade-action"); const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"); const linter_tags_1 = require("../linter-tags"); const alias_tracking_1 = require("../../dataflow/eval/resolve/alias-tracking"); const general_1 = require("../../dataflow/eval/values/general"); const config_1 = require("../../config"); const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const r_value_1 = require("../../dataflow/eval/values/r-value"); exports.SEEDED_RANDOMNESS = { createSearch: (config) => flowr_search_builder_1.Q.all() .with(search_enrichers_1.Enrichment.CallTargets, { onlyBuiltin: true }) .filter({ name: flowr_search_filters_1.FlowrFilter.MatchesEnrichment, args: { enrichment: search_enrichers_1.Enrichment.CallTargets, test: (0, flowr_search_filters_1.testFunctionsIgnoringPackage)(config.randomnessConsumers) } }) .with(search_enrichers_1.Enrichment.LastCall, [ ...config.randomnessProducers.filter(p => p.type === 'function').map(p => ({ callName: p.name })), ...getDefaultAssignments().flatMap(b => b.names).map(a => ({ callName: a, cascadeIf: () => cascade_action_1.CascadeAction.Continue })) ]), processSearchResult: (elements, config, { dataflow }) => { const assignmentProducers = new Set(config.randomnessProducers.filter(p => p.type == 'assignment').map(p => p.name)); const assignmentArgIndexes = new Map(getDefaultAssignments().flatMap(a => a.names.map(n => ([n, a.config?.swapSourceAndTarget ? 1 : 0])))); const metadata = { consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0 }; return { results: elements.getElements() // map and filter consumers .flatMap(element => (0, search_enrichers_1.enrichmentContent)(element, search_enrichers_1.Enrichment.CallTargets).targets.map(target => { metadata.consumerCalls++; return { range: element.node.info.fullRange, target: target, searchElement: element }; })) // filter by calls that aren't preceded by a randomness producer .filter(element => { const producers = (0, search_enrichers_1.enrichmentContent)(element.searchElement, search_enrichers_1.Enrichment.LastCall).linkedIds .map(e => dataflow.graph.getVertex(e.node.info.id)); const { assignment, func } = Object.groupBy(producers, f => assignmentArgIndexes.has(f.name) ? 'assignment' : 'func'); let nonConstant = false; // function calls are already taken care of through the LastCall enrichment itself for (const f of func ?? []) { if (isConstantArgument(dataflow.graph, f, 0)) { metadata.callsWithFunctionProducers++; return false; } else { nonConstant = true; } } // assignments have to be queried for their destination for (const a of assignment ?? []) { const argIdx = assignmentArgIndexes.get(a.name); const dest = (0, graph_1.getReferenceOfArgument)(a.args[argIdx]); if (dest !== undefined && assignmentProducers.has((0, node_id_1.recoverName)(dest, dataflow.graph.idMap))) { if (isConstantArgument(dataflow.graph, a, 1 - argIdx)) { metadata.callsWithAssignmentProducers++; return false; } else { nonConstant = true; } } } if (nonConstant) { metadata.callsWithNonConstantProducers++; } return true; }) .map(element => ({ certainty: linter_format_1.LintingResultCertainty.Certain, function: element.target, range: element.range })), '.meta': metadata }; }, info: { defaultConfig: { randomnessProducers: [{ type: 'function', name: 'set.seed' }, { type: 'assignment', name: '.Random.seed' }], randomnessConsumers: ['jitter', 'sample', 'sample.int', 'arima.sim', 'kmeans', 'princomp', 'rcauchy', 'rchisq', 'rexp', 'rgamma', 'rgeom', 'rlnorm', 'rlogis', 'rmultinom', 'rnbinom', 'rnorm', 'rpois', 'runif', 'pointLabel', 'some', 'rbernoulli', 'rdunif', 'generateSeedVectors'], }, tags: [linter_tags_1.LintingRuleTag.Robustness, linter_tags_1.LintingRuleTag.Reproducibility], // only finds proper randomness producers and consumers due to its config, but will not find all producers/consumers since not all existing deprecated functions will be in the config certainty: linter_format_1.LintingRuleCertainty.BestEffort, name: 'Seeded Randomness', description: 'Checks whether randomness-based function calls are preceded by a random seed generation function. For consistent reproducibility, functions that use randomness should only be called after a constant random seed is set using a function like `set.seed`.' }, prettyPrint: { [linter_format_1.LintingPrettyPrintContext.Query]: (result, _meta) => `Function \`${result.function}\` at ${(0, dfg_1.formatRange)(result.range)}`, [linter_format_1.LintingPrettyPrintContext.Full]: (result, _meta) => `Function \`${result.function}\` at ${(0, dfg_1.formatRange)(result.range)} is called without a preceding random seed function like \`set.seed\`` } }; function getDefaultAssignments() { return default_builtin_config_1.DefaultBuiltinConfig.filter(b => b.type === 'function' && b.processor == 'builtin:assignment'); } function isConstantArgument(graph, call, argIndex) { const args = call.args.filter(arg => arg !== r_function_call_1.EmptyArgument && !arg.name).map(graph_1.getReferenceOfArgument); const values = (0, general_1.valueSetGuard)((0, alias_tracking_1.resolveIdToValue)(args[argIndex], { graph: graph, resolve: config_1.VariableResolve.Alias })); return values?.elements.every(v => v.type === 'number' || v.type === 'logical' || v.type === 'string' || v.type === 'interval' && v.startInclusive && v.endInclusive && v.start.type === 'number' && v.end.type === 'number' && (0, r_value_1.asValue)(v.start.value).num === (0, r_value_1.asValue)(v.end.value).num) ?? false; } //# sourceMappingURL=seeded-randomness.js.map