Skip to content
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,26 @@ It offers a wide variety of features, for example:
╰ Absolute Paths (absolute-file-paths):
╰ certain:
╰ Path `/root/x.txt` at 1.1-23
╰ Metadata: totalConsidered: 1, totalUnknown: 0, searchTimeMs: 1, processTimeMs: 0
╰ Metadata: totalConsidered: 1, totalUnknown: 0, searchTimeMs: 0, processTimeMs: 0
╰ Unused Definitions (unused-definitions):
╰ Metadata: totalConsidered: 0, searchTimeMs: 0, processTimeMs: 0
╰ Naming Convention (naming-convention):
╰ Metadata: numMatches: 0, numBreak: 0, searchTimeMs: 0, processTimeMs: 0
╰ Network Functions (network-functions):
╰ Metadata: totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0
╰ Dataframe Access Validation (dataframe-access-validation):
╰ Metadata: numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 0
╰ Metadata: numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 1
╰ Dead Code (dead-code):
╰ Metadata: consideredNodes: 5, searchTimeMs: 0, processTimeMs: 0
╰ Useless Loops (useless-loop):
╰ Metadata: numOfUselessLoops: 0, searchTimeMs: 0, processTimeMs: 0
╰ Problematic inputs (problematic-inputs):
╰ Metadata: searchTimeMs: 0, processTimeMs: 0
╰ Stop without call.=False argument (stop-call):
╰ Metadata: consideredNodes: 0, searchTimeMs: 1, processTimeMs: 0
╰ Metadata: consideredNodes: 0, searchTimeMs: 0, processTimeMs: 0
╰ Roxygen Arguments (roxygen-arguments):
╰ Metadata: searchTimeMs: 0, processTimeMs: 0
All queries together required ≈2 ms (1ms accuracy, total 3 ms)
All queries together required ≈2 ms (1ms accuracy, total 2 ms)
```


Expand All @@ -86,9 +86,9 @@ It offers a wide variety of features, for example:

_Results (prettified and summarized):_

Query: **linter** (2 ms)\
Query: **linter** (1 ms)\
   ╰ **Deprecated Functions** (deprecated-functions):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 1, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;╰ **File Path Validity** (file-path-validity):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ certain:\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ Path `/root/x.txt` at 1.1-23\
Expand Down Expand Up @@ -121,7 +121,7 @@ It offers a wide variety of features, for example:

<details> <summary style="color:gray">Show Detailed Results as Json</summary>

The analysis required _2.4 ms_ (including parsing and normalization and the query) within the generation environment.
The analysis required _2.0 ms_ (including parsing and normalization and the query) within the generation environment.

In general, the JSON contains the Ids of the nodes in question as they are present in the normalized AST or the dataflow graph of flowR.
Please consult the [Interface](https://github.com/flowr-analysis/flowr/wiki/Interface) wiki page for more information on how to get those.
Expand All @@ -138,7 +138,7 @@ It offers a wide variety of features, for example:
".meta": {
"totalCalls": 0,
"totalFunctionDefinitions": 0,
"searchTimeMs": 1,
"searchTimeMs": 0,
"processTimeMs": 0
}
},
Expand Down Expand Up @@ -273,7 +273,7 @@ It offers a wide variety of features, for example:
}
},
".meta": {
"timing": 2
"timing": 1
}
},
".meta": {
Expand Down Expand Up @@ -436,6 +436,7 @@ It offers a wide variety of features, for example:

```text
https://mermaid.live/view#base64:eyJjb2RlIjoiZmxvd2NoYXJ0IEJUXG4gICAgMChbXCJgIzkxO1JTeW1ib2wjOTM7IHRlc3RcbiAgICAgICgwKVxuICAgICAgKjEuMS00KmBcIl0pXG4gICAlJSBObyBlZGdlcyBmb3VuZCBmb3IgMFxuICAgIDEoW1wiYCM5MTtSU3ltYm9sIzkzOyB0ZXN0ZmlsZXNcbiAgICAgICgxKVxuICAgICAgKjEuNi0xNCpgXCJdKVxuICAgJSUgTm8gZWRnZXMgZm91bmQgZm9yIDFcbiAgICAyW1tcImAjOTE7UkJpbmFyeU9wIzkzOyAvXG4gICAgICAoMilcbiAgICAgICoxLjEtMTQqXG4gICAgKDAsIDEpYFwiXV1cbiAgICBidWlsdC1pbjpfW1wiYEJ1aWx0LUluOlxuL2BcIl1cbiAgICBzdHlsZSBidWlsdC1pbjpfIHN0cm9rZTpncmF5LGZpbGw6Z3JheSxzdHJva2Utd2lkdGg6MnB4LG9wYWNpdHk6Ljg7XG4gICAgMyhbXCJgIzkxO1JTeW1ib2wjOTM7IGV4YW1wbGUuUlxuICAgICAgKDMpXG4gICAgICAqMS4xNi0yNCpgXCJdKVxuICAgJSUgTm8gZWRnZXMgZm91bmQgZm9yIDNcbiAgICA0W1tcImAjOTE7UkJpbmFyeU9wIzkzOyAvXG4gICAgICAoNClcbiAgICAgICoxLjEtMjQqXG4gICAgKDIsIDMpYFwiXV1cbiAgICAyIC0tPnxcInJlYWRzLCBhcmd1bWVudFwifCAwXG4gICAgMiAtLT58XCJyZWFkcywgYXJndW1lbnRcInwgMVxuICAgIDIgLS4tPnxcInJlYWRzLCBjYWxsc1wifCBidWlsdC1pbjpfXG4gICAgbGlua1N0eWxlIDIgc3Ryb2tlOmdyYXk7XG4gICAgNCAtLT58XCJyZWFkcywgYXJndW1lbnRcInwgMlxuICAgIDQgLS0+fFwicmVhZHMsIGFyZ3VtZW50XCJ8IDNcbiAgICA0IC0uLT58XCJyZWFkcywgY2FsbHNcInwgYnVpbHQtaW46X1xuICAgIGxpbmtTdHlsZSA1IHN0cm9rZTpncmF5OyIsIm1lcm1haWQiOnsiYXV0b1N5bmMiOnRydWV9fQ==
Copied mermaid url to clipboard (dataflow: 0ms).
```


Expand Down Expand Up @@ -734,7 +735,7 @@ It offers a wide variety of features, for example:
```


(The analysis required _1.8 ms_ (including parse and normalize, using the [tree-sitter](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)
(The analysis required _1.6 ms_ (including parse and normalize, using the [tree-sitter](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)



Expand Down
3 changes: 3 additions & 0 deletions src/dataflow/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export class Environment implements IEnvironment {
readonly id: number;
/** Optional name for namespaced/non-anonymous environments, please only set if you know what you are doing */
n?: string;
/** to keep track if/whether environment was added as package/namespace/imports environment */
t?: 'package' |'namespace' |'imports';
/** if created by a closure, the node id of that closure */
private c?: NodeId;
parent: Environment;
Expand Down Expand Up @@ -92,6 +94,7 @@ export class Environment implements IEnvironment {
const clone = new Environment(parent, this.builtInEnv);
clone.c = this.c;
clone.n = this.n;
clone.t = this.t;
clone.memory = new Map(
this.memory.entries()
.map(([k, v]) => [k,
Expand Down
1 change: 1 addition & 0 deletions src/dataflow/eval/resolve/alias-tracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export function trackAliasInEnvironments(identifier: Identifier | undefined, env
}

const defs = resolveByNameAnyType(identifier, environment);

if(defs === undefined) {
return Top;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@ import { processKnownFunctionCall } from '../known-call-handling';
import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
import type { PotentiallyEmptyRArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { dataflowLogger } from '../../../../../logger';
import { unpackNonameArg } from '../argument/unpack-argument';
import type { RString } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-string';
import { RType } from '../../../../../../r-bridge/lang-4.x/ast/model/type';
import { wrapArgumentsUnnamed } from '../argument/make-argument';
import { Identifier } from '../../../../../environments/identifier';
import { Identifier, ReferenceType } from '../../../../../environments/identifier';
import { BuiltInProcName } from '../../../../../environments/built-in-proc-name';
import { Environment } from '../../../../../environments/environment';
import { EdgeType } from '../../../../../graph/edge';
import { isUndefined } from '../../../../../../util/assert';
import { RArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-argument';
import { resolveIdToValue } from '../../../../../eval/resolve/alias-tracking';
import { valueSetGuard } from '../../../../../eval/values/general';
import type { Package } from '../../../../../../project/plugins/package-version-plugins/package';
import type { NamespaceInfo } from '../../../../../../project/plugins/file-plugins/files/flowr-namespace-file';
import { convertFnArguments } from '../common';
import { pMatch } from '../../../../linker';
import type { Lift, TernaryLogical } from '../../../../../eval/values/r-value';
import { VertexType } from '../../../../../graph/vertex';

/**
* Process a library call like `library` or `require`
Expand All @@ -23,21 +34,32 @@ export function processLibrary<OtherInfo>(
data: DataflowProcessorInformation<OtherInfo & ParentInformation>
): DataflowInformation {
/* we do not really know what loading the library does and what side effects it causes, hence we mark it as an unknown side effect */
if(args.length !== 1) {
dataflowLogger.warn(`Currently only one-arg library-likes are allows (for ${Identifier.toString(name.content)}), skipping`);
//const characterOnlyArg = args.map(v => v as RArgument).find(v => v.lexeme === 'character.only');
//const characterOnly = characterOnlyArg?.value?.type === 'RLogical' && characterOnlyArg?.value?.content === true;
if(args.length === 0){
return processKnownFunctionCall({ name, args, rootId, data, hasUnknownSideEffect: true, origin: 'default' }).information;
}
const nameToLoad = unpackNonameArg(args[0]);
if(nameToLoad === undefined || nameToLoad.type !== RType.Symbol) {
const params = {
'package': 'pkg',
'character.only': 'char'
};
const resolveArgs = { environment: data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables, ctx: data.ctx };
const argMaps = pMatch(convertFnArguments(args), params);
const packageId = Array.from(new Set(argMaps.get('pkg')));
const charId = Array.from(new Set(argMaps.get('char')));

const nameToLoad = RArgument.getValue<OtherInfo & ParentInformation>(args, packageId[0]);

if(nameToLoad === undefined || nameToLoad.type !== RType.Symbol && nameToLoad.type !== RType.String){
//if(nameToLoad === undefined || nameToLoad.type !== RType.Symbol && !characterOnly || characterOnly && nameToLoad.type !== RType.Symbol && nameToLoad.type !== RType.String) {
dataflowLogger.warn('No library name provided, skipping');
return processKnownFunctionCall({ name, args, rootId, data, hasUnknownSideEffect: true, origin: 'default' }).information;
}
if(Identifier.getNamespace(nameToLoad.content) !== undefined) {
if(Identifier.getNamespace(nameToLoad.type === RType.String ? nameToLoad.content.str : nameToLoad.content) !== undefined) {
dataflowLogger.warn('Namespaced library names are not supported, ignoring namespace');
}

// treat as a function call but convert the first argument to a string
const newArg: RString<OtherInfo & ParentInformation> = {
const newArg: RString<OtherInfo & ParentInformation> = nameToLoad.type === RType.String ? nameToLoad : {
type: RType.String,
info: nameToLoad.info,
lexeme: nameToLoad.lexeme,
Expand All @@ -47,9 +69,159 @@ export function processLibrary<OtherInfo>(
str: Identifier.getName(nameToLoad.content)
}
};
return processKnownFunctionCall({
name, args: wrapArgumentsUnnamed([newArg], data.completeAst.idMap), rootId, data,
hasUnknownSideEffect: true,

let packetName = nameToLoad?.lexeme;
let isCharacterOnly: Lift<TernaryLogical> = false;
if(charId.length >= 1){
const values = valueSetGuard(resolveIdToValue(charId[0], resolveArgs));
if(values?.type === 'set' && values.elements.length === 1 && values.elements[0].type === 'logical') {
isCharacterOnly = values.elements[0].value;
}
}
if(isCharacterOnly !== false){
const values = valueSetGuard(resolveIdToValue(nameToLoad.info.id, resolveArgs));
if(values?.type === 'set' && values.elements.length !== 0){
if(values.elements[0].type === 'string' && 'str' in values.elements[0].value){
packetName = values.elements[0].value.str;
}
}
//else fälle?
}
// TODO: check whether we know something about the library and if so, do **not** mark it as an unkown side effect
const info = processKnownFunctionCall({
name,
args: wrapArgumentsUnnamed([newArg, ...args.slice(1)], data.completeAst.idMap), rootId, data,
hasUnknownSideEffect: false,
origin: BuiltInProcName.Library
}).information;
const dependency = data.ctx.deps.getDependency(packetName);
if(dependency){
linkLibrary(dependency, info, rootId, data);
} else {
info.graph.markIdForUnknownSideEffects(rootId);
}
return info;
}

function getGlobalEnv(info: DataflowInformation){
if(info.environment.level < 0){
return undefined;
}
let env = info.environment.current;
for(let i = 0; i < info.environment.level; i++){
env = env.parent;
}
return env;
}

function linkLibrary<OtherInfo>(dependency: Package, info: DataflowInformation, rootId: NodeId, data: DataflowProcessorInformation<OtherInfo & ParentInformation>) {
const globalEnv = getGlobalEnv(info);
if(isUndefined(globalEnv) || isUndefined(dependency.namespaceInfo)){
return;
}
const pack = dependency.name;
const functions = dependency.namespaceInfo.exportedSymbols;
/*
//add namespace environment
let namespaceEnv = new Environment(globalEnv);
namespaceEnv.n = pack;
namespaceEnv.t = 'namespace';
for(const func of functions){
namespaceEnv = namespaceEnv.define({
name: Identifier.make(func, pack),
type: ReferenceType.Function,
nodeId: NodeId.toBuiltIn(func),
definedAt: NodeId.toBuiltIn(pack),
});
info.environment = {
level: info.environment.level + 1,
current: namespaceEnv
};
}
*/
//add package environment
const oldGlobParent = globalEnv.parent;
let packageEnv = new Environment(oldGlobParent);
packageEnv.n = pack;
packageEnv.t = 'package';
for(const func of functions){
packageEnv = packageEnv.define({
name: Identifier.make(func, pack),
type: ReferenceType.Function,
nodeId: NodeId.toBuiltIn(func),
definedAt: NodeId.toBuiltIn(pack),
});
info.graph.addVertex({
tag: VertexType.FunctionDefinition,
id: NodeId.toBuiltIn(func),
environment: info.environment,
cds: data.cds,
params: {},
subflow: {
graph: new Set(),
unknownReferences: [],
in: [],
out: [],
environment: info.environment,
entryPoint: NodeId.toBuiltIn(func),
hooks: []
},
exitPoints: [],
}, data.ctx.env.makeCleanEnv());
//info.graph.addVertex(NodeId.toBuiltIn(func) as string, info.environment);
info.graph.addEdge(NodeId.toBuiltIn(func), rootId, EdgeType.Reads | EdgeType.Calls);
}
globalEnv.parent = packageEnv;
//info environment shouldn't change if we always return globalEnv as current
/*info.environment = {
level: info.environment.level + 1,
current: info.environment.current
};*/
//add imports environment
let importsEnv: Environment = new Environment(oldGlobParent);
importsEnv.n = pack;
importsEnv.t = 'imports';
importsEnv = recImports(importsEnv, dependency.namespaceInfo, data, new Set());
//info environment shouldn't change if we always return globalEnv as current
/*info.environment = {
level: info.environment.level + 1,
current: info.environment.current
};*/
packageEnv.parent = importsEnv;
}

function recImports<OtherInfo>(importsEnv: Environment, namespaceInfo: NamespaceInfo, data: DataflowProcessorInformation<OtherInfo & ParentInformation>, alreadyImportedAll: Set<string>){
for(const imp of namespaceInfo.importedPackages){
const importedDependency = data.ctx.deps.getDependency(imp[0]);
if(isUndefined(importedDependency)){
continue;
}
const funcToImport: string[] | undefined = imp[1] === 'all' ? importedDependency?.namespaceInfo?.exportedSymbols : importedDependency?.namespaceInfo?.exportedSymbols.filter(v => (imp[1] as string[]).includes(v));
if(isUndefined(funcToImport)){
continue;
}
if(alreadyImportedAll.has(importedDependency.name)){
continue;
}
for(const func of funcToImport){
if(importsEnv.memory.has(importedDependency.name + ':' + func)){
continue;
}
importsEnv = importsEnv.define({
name: Identifier.make(importedDependency.name + ':' + func, importsEnv.n),
type: ReferenceType.Function,
nodeId: NodeId.toBuiltIn(func),
definedAt: NodeId.toBuiltIn(importedDependency.name)
});
}
if(imp[1] === 'all'){
alreadyImportedAll.add(importedDependency.name);
}
//info.graph.addEdge(rootId, NodeId.toBuiltIn((importedDependency as Package).name), EdgeType.Reads | EdgeType.Calls);
//if only importFrom() we don't have to recursively import
if(imp[1] === 'all' && importedDependency?.namespaceInfo){
importsEnv = recImports(importsEnv, importedDependency.namespaceInfo, data, alreadyImportedAll);
}
}
return importsEnv;
}
Loading
Loading