Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ It offers a wide variety of features, for example:
╰ Path `/root/x.txt` at 1.1-23
╰ Metadata: totalReads: 1, totalUnknown: 0, totalWritesBeforeAlways: 0, totalValid: 0, searchTimeMs: 0, processTimeMs: 0
╰ Seeded Randomness (seeded-randomness):
╰ Metadata: consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 1, processTimeMs: 0
╰ Metadata: consumerCalls: 0, callsWithFunctionProducers: 0, callsWithAssignmentProducers: 0, callsWithNonConstantProducers: 0, callsWithOtherBranchProducers: 0, searchTimeMs: 0, processTimeMs: 0
╰ Absolute Paths (absolute-file-paths):
╰ certain:
╰ Path `/root/x.txt` at 1.1-23
Expand All @@ -51,7 +51,7 @@ It offers a wide variety of features, for example:
╰ 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
╰ Metadata: totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 1
╰ Dataframe Access Validation (dataframe-access-validation):
╰ Metadata: numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 0
╰ Dead Code (dead-code):
Expand Down Expand Up @@ -80,9 +80,9 @@ It offers a wide variety of features, for example:

_Results (prettified and summarized):_

Query: **linter** (3 ms)\
Query: **linter** (4 ms)\
   ╰ **Deprecated Functions** (deprecated-functions):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 2, 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 All @@ -92,24 +92,24 @@ It offers a wide variety of features, for example:
&nbsp;&nbsp;&nbsp;╰ **Absolute Paths** (absolute-file-paths):\
&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\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalConsidered: 1, totalUnknown: 0, searchTimeMs: 1, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalConsidered: 1, totalUnknown: 0, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;╰ **Unused Definitions** (unused-definitions):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalConsidered: 0, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;╰ **Naming Convention** (naming-convention):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>numMatches: 0, numBreak: 0, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;╰ **Network Functions** (network-functions):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>totalCalls: 0, totalFunctionDefinitions: 0, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;╰ **Dataframe Access Validation** (dataframe-access-validation):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>numOperations: 0, numAccesses: 0, totalAccessed: 0, searchTimeMs: 0, processTimeMs: 1</code>\
&nbsp;&nbsp;&nbsp;╰ **Dead Code** (dead-code):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>consideredNodes: 5, searchTimeMs: 0, processTimeMs: 0</code>\
&nbsp;&nbsp;&nbsp;╰ **Useless Loops** (useless-loop):\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;╰ _Metadata_: <code>numOfUselessLoops: 0, searchTimeMs: 0, processTimeMs: 0</code>\
_All queries together required ≈3 ms (1ms accuracy, total 3 ms)_
_All queries together required ≈4 ms (1ms accuracy, total 4 ms)_

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

The analysis required _3.3 ms_ (including parsing and normalization and the query) within the generation environment.
The analysis required _4.3 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 @@ -126,7 +126,7 @@ It offers a wide variety of features, for example:
".meta": {
"totalCalls": 0,
"totalFunctionDefinitions": 0,
"searchTimeMs": 0,
"searchTimeMs": 2,
"processTimeMs": 0
}
},
Expand Down Expand Up @@ -180,7 +180,7 @@ It offers a wide variety of features, for example:
".meta": {
"totalConsidered": 1,
"totalUnknown": 0,
"searchTimeMs": 1,
"searchTimeMs": 0,
"processTimeMs": 0
}
},
Expand Down Expand Up @@ -217,7 +217,7 @@ It offers a wide variety of features, for example:
"numAccesses": 0,
"totalAccessed": 0,
"searchTimeMs": 0,
"processTimeMs": 0
"processTimeMs": 1
}
},
"dead-code": {
Expand All @@ -238,11 +238,11 @@ It offers a wide variety of features, for example:
}
},
".meta": {
"timing": 3
"timing": 4
}
},
".meta": {
"timing": 3
"timing": 4
}
}
```
Expand Down Expand Up @@ -321,7 +321,7 @@ It offers a wide variety of features, for example:
N <- 10
for(i in 1:(N-1)) sum <- sum + i + w
sum
All queries together required ≈3 ms (1ms accuracy, total 3 ms)
All queries together required ≈4 ms (1ms accuracy, total 4 ms)
```


Expand Down Expand Up @@ -401,6 +401,7 @@ It offers a wide variety of features, for example:

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


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


(The analysis required _2.3 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.4 ms_ (including parse and normalize, using the [tree-sitter](https://github.com/flowr-analysis/flowr/wiki/Engines) engine) within the generation environment.)



Expand Down
58 changes: 58 additions & 0 deletions src/dataflow/fn/exceptions-of-function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import type { CallGraph } from '../graph/call-graph';
import { VertexType } from '../graph/vertex';
import type { ControlDependency } from '../info';
import { ExitPointType } from '../info';
import type { BuiltInMappingName } from '../environments/built-in';
import { isBuiltIn } from '../environments/built-in';

const CatchHandlers: ReadonlySet<string> = new Set<BuiltInMappingName>(['builtin:try']);
export interface ExceptionPoint {
id: NodeId;
cds?: readonly ControlDependency[] | undefined;
}
/**
* Collect exception sources of a function in the call graph.
* This returns the `NodeId`s of functions that may throw exceptions when called by the given function.
* Please be aware, that these are restricted to functions known by flowR.
*/
export function calculateExceptionsOfFunction(id: NodeId, graph: CallGraph): ExceptionPoint[] {
const collectedExceptions: ExceptionPoint[] = [];
const visited = new Set<NodeId>();
const toVisit: NodeId[] = [id];

while(toVisit.length > 0) {
const currentId = toVisit.pop() as NodeId;
if(visited.has(currentId)) {
continue;
}
visited.add(currentId);

const vtx = graph.getVertex(currentId);
if(!vtx) {
continue;
}

if(isBuiltIn(currentId)) {
continue;
}

if(vtx.tag === VertexType.FunctionDefinition) {
for(const e of vtx.exitPoints.filter(e => e.type === ExitPointType.Error)) {
if(!collectedExceptions.find(x => x.id === e.nodeId)) {
collectedExceptions.push({ id: e.nodeId, cds: e.controlDependencies });
}
}
} else if(vtx.tag === VertexType.FunctionCall && vtx.origin !== 'unnamed' && vtx.origin.some(c => CatchHandlers.has(c))) {
// skip the try-catch handlers as they catch all exceptions within their body
continue;
}

const outEdges = graph.outgoingEdges(currentId) ?? [];
for(const [out] of outEdges) {
toVisit.push(out);
}
}

return Array.from(collectedExceptions);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DataflowProcessorInformation } from '../../../../../processor';
import type { ExitPoint, DataflowInformation } from '../../../../../info';
import { ExitPointType } from '../../../../../info';
import type { ControlDependency, DataflowInformation, ExitPoint } from '../../../../../info';
import { ExitPointType, happensInEveryBranch } from '../../../../../info';
import { processKnownFunctionCall } from '../known-call-handling';
import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
import {
Expand All @@ -11,10 +11,13 @@ import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/node
import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { dataflowLogger } from '../../../../../logger';
import { pMatch } from '../../../../linker';
import type { DataflowGraphVertexInfo } from '../../../../../graph/vertex';
import { VertexType } from '../../../../../graph/vertex';
import { tryUnpackNoNameArg } from '../argument/unpack-argument';
import type { DataflowGraph } from '../../../../../graph/graph';
import { RType } from '../../../../../../r-bridge/lang-4.x/ast/model/type';
import { isUndefined } from '../../../../../../util/assert';
import { EdgeType } from '../../../../../graph/edge';


function getArgsOfName(argMaps: Map<NodeId, string>, name: string): Set<NodeId> {
Expand Down Expand Up @@ -61,43 +64,86 @@ export function processTryCatch<OtherInfo>(
const errorArg = getArgsOfName(argMaps, 'error');
const finallyArg = getArgsOfName(argMaps, 'finally');
// only take those exit points from the block
// check whether blockArg has *always* happening exceptions, if so we do not constrain the error handler
const blockErrorExitPoints: (ControlDependency | undefined)[] = [];
const errorExitPoints: ExitPoint[] = [];
(info.exitPoints as ExitPoint[]) = res.processedArguments.flatMap(arg => {
if(!arg) {
return [];
}
// this calls error and finally args
if(finallyArg.has(arg.entryPoint) || errorArg.has(arg.entryPoint)) {
return handleFdefAsCalled(arg.entryPoint, info.graph, arg.exitPoints);
if(finallyArg.has(arg.entryPoint)) {
return handleFdefAsCalled(arg.entryPoint, info.graph, arg.exitPoints, undefined);
} else if(errorArg.has(arg.entryPoint)) {
errorExitPoints.push(...getExitPoints(info.graph.getVertex(arg.entryPoint), info.graph) ?? []);
}
if(!blockArg.has(arg.entryPoint)) {
// not killing other args
return arg.exitPoints;
}
blockErrorExitPoints.push(...arg.exitPoints.filter(ep => ep.type === ExitPointType.Error).flatMap(a => a.controlDependencies));
return arg.exitPoints.filter(ep => ep.type !== ExitPointType.Error);
});


if(errorExitPoints.length > 0) {
if(happensInEveryBranch(blockErrorExitPoints.some(isUndefined) ? undefined : blockErrorExitPoints as ControlDependency[])) {
(info.exitPoints as ExitPoint[]).push(...errorExitPoints);
} else {
(info.exitPoints as ExitPoint[]).push(...constrainExitPoints(errorExitPoints, blockArg));
}
}
for(const e of errorArg) {
info.graph.addEdge(rootId, e, EdgeType.Calls);
}
for(const f of finallyArg) {
info.graph.addEdge(rootId, f, EdgeType.Calls);
}
for(const e of info.exitPoints) {
if(e.type !== ExitPointType.Error) {
info.graph.addEdge(rootId, e.nodeId, EdgeType.Returns);
}
}
return info;
}

function handleFdefAsCalled(nodeId: NodeId, graph: DataflowGraph, def: readonly ExitPoint[]): readonly ExitPoint[] {
const v = graph.getVertex(nodeId);
if(!v) {
return def;
function getExitPoints(vertex: DataflowGraphVertexInfo | undefined, graph: DataflowGraph): readonly ExitPoint[] | undefined {
if(!vertex) {
return undefined;
}
if(v.tag === VertexType.FunctionDefinition) {
return v.exitPoints;
if(vertex.tag === VertexType.FunctionDefinition) {
return vertex.exitPoints;
}
// we assumed named argument
const n = graph.idMap?.get(nodeId);
const n = graph.idMap?.get(vertex.id);
if(!n) {
return def;
return undefined;
}
if(n.type === RType.Argument && n.value?.type === RType.FunctionDefinition) {
const fdefV = graph.getVertex(n.value.info.id);
if(fdefV?.tag === VertexType.FunctionDefinition) {
return fdefV.exitPoints;
}
}
return def;
return undefined;
}

function handleFdefAsCalled(nodeId: NodeId, graph: DataflowGraph, def: readonly ExitPoint[], constrain: Set<NodeId> | undefined): readonly ExitPoint[] {
const v = graph.getVertex(nodeId);
const e = getExitPoints(v, graph);
return e ? constrainExitPoints(e, constrain) : def;
}

function constrainExitPoints(exitPoints: readonly ExitPoint[], constrain: Set<NodeId> | undefined): readonly ExitPoint[] {
if(!constrain || constrain.size === 0) {
return exitPoints;
}
// append constrains with true
const cds = Array.from(constrain, id => ({ id, when: true }));
return exitPoints.map(e => {
if(e.controlDependencies) {
e.controlDependencies.push(...cds);
return e;
} else {
return { ...e, controlDependencies: cds };
}
});
}
1 change: 1 addition & 0 deletions src/documentation/doc-util/doc-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface QueryDocumentation {
readonly type: 'virtual' | 'active';
readonly shortDescription: string;
readonly functionName: string;
/** Path to the file implementing the query function, the wiki generation will fail if this isnt found */
readonly functionFile: string;
readonly buildExplanation: (shell: RShell, ctx: GeneralDocContext) => Promise<string>;
}
Expand Down
34 changes: 34 additions & 0 deletions src/documentation/wiki-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { executeFileQuery } from '../queries/catalog/files-query/files-query-exe
import { executeCallGraphQuery } from '../queries/catalog/call-graph-query/call-graph-query-executor';
import { executeRecursionQuery } from '../queries/catalog/inspect-recursion-query/inspect-recursion-query-executor';
import { executeDoesCallQuery } from '../queries/catalog/does-call-query/does-call-query-executor';
import { executeExceptionQuery } from '../queries/catalog/inspect-exceptions-query/inspect-exception-query-executor';


registerQueryDocumentation('call-context', {
Expand Down Expand Up @@ -373,6 +374,39 @@ ${
}
});

registerQueryDocumentation('inspect-exception', {
name: 'Inspect Exceptions of Functions Query',
type: 'active',
shortDescription: 'Determine whether functions throw exceptions (known to flowR)',
functionName: executeExceptionQuery.name,
functionFile: '../queries/catalog/inspect-exceptions-query/inspect-exception-query-executor.ts',
buildExplanation: async(shell: RShell) => {
const exampleCode = `mayFail <- function(x) {
if(x < 0) stop("Negative value!")
else sqrt(x)
}
safeFail <- function(x) {
tryCatch(
mayFail(x),
error = function(e) { NA }
)
}`;
return `
With this query you can identify which functions in the code throw exceptions (known to flowR).

Using the following example code:
${codeBlock('r', exampleCode)}
the following query returns the information for all identified function definitions whether they throw exceptions:
${
await showQuery(shell, exampleCode, [{
type: 'inspect-exception',
}], { showCode: true, collapseQuery: true })
}
`;
}
});


registerQueryDocumentation('origin', {
name: 'Origin Query',
type: 'active',
Expand Down
Loading
Loading