Skip to content

Commit f7eabd8

Browse files
authored
feat: error if call leads to infinite recursion (#783)
Closes #667 ### Summary of Changes Show an error if a call leads to infinite recursion.
1 parent 34bf182 commit f7eabd8

File tree

10 files changed

+84
-200
lines changed

10 files changed

+84
-200
lines changed

packages/safe-ds-lang/src/language/validation/other/expressions/calls.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getArguments, Parameter } from '../../../helpers/nodeProperties.js';
44
import { SafeDsServices } from '../../../safe-ds-module.js';
55

66
export const CODE_CALL_CONSTANT_ARGUMENT = 'call/constant-argument';
7+
export const CODE_CALL_INFINITE_RECURSION = 'call/infinite-recursion';
78

89
export const callArgumentMustBeConstantIfParameterIsConstant = (services: SafeDsServices) => {
910
const nodeMapper = services.helpers.NodeMapper;
@@ -31,3 +32,16 @@ export const callArgumentMustBeConstantIfParameterIsConstant = (services: SafeDs
3132
}
3233
};
3334
};
35+
36+
export const callMustNotBeRecursive = (services: SafeDsServices) => {
37+
const callGraphComputer = services.flow.CallGraphComputer;
38+
39+
return (node: SdsCall, accept: ValidationAcceptor) => {
40+
if (callGraphComputer.isRecursive(node)) {
41+
accept('error', 'Call leads to infinite recursion.', {
42+
node,
43+
code: CODE_CALL_INFINITE_RECURSION,
44+
});
45+
}
46+
};
47+
};

packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ import {
8686
typeParameterMustHaveSufficientContext,
8787
typeParameterMustNotBeUsedInNestedNamedTypeDeclarations,
8888
} from './other/declarations/typeParameters.js';
89-
import { callArgumentMustBeConstantIfParameterIsConstant } from './other/expressions/calls.js';
89+
import { callArgumentMustBeConstantIfParameterIsConstant, callMustNotBeRecursive } from './other/expressions/calls.js';
9090
import { divisionDivisorMustNotBeZero } from './other/expressions/infixOperations.js';
9191
import {
9292
lambdaMustBeAssignedToTypedParameter,
@@ -223,6 +223,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
223223
SdsCall: [
224224
callArgumentListShouldBeNeeded(services),
225225
callArgumentMustBeConstantIfParameterIsConstant(services),
226+
callMustNotBeRecursive(services),
226227
callReceiverMustBeCallable(services),
227228
],
228229
SdsCallableType: [

packages/safe-ds-lang/tests/language/flow/safe-ds-call-graph-computer.test.ts

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { describe, expect, it } from 'vitest';
22
import { EmptyFileSystem, isNamed } from 'langium';
3-
import {
4-
isSdsBlockLambda,
5-
isSdsCall,
6-
isSdsExpressionLambda,
7-
isSdsModule,
8-
SdsCall,
9-
} from '../../../src/language/generated/ast.js';
3+
import { isSdsBlockLambda, isSdsExpressionLambda, isSdsModule, SdsCall } from '../../../src/language/generated/ast.js';
104
import { createSafeDsServices } from '../../../src/language/index.js';
115
import { createCallGraphTests } from './creator.js';
126
import { getNodeOfType } from '../../helpers/nodeFinder.js';
@@ -18,40 +12,6 @@ const services = createSafeDsServices(EmptyFileSystem).SafeDs;
1812
const callGraphComputer = services.flow.CallGraphComputer;
1913

2014
describe('SafeDsCallGraphComputer', () => {
21-
describe('isRecursive', () => {
22-
it('should return true for recursive calls', async () => {
23-
const call = await getNodeOfType(
24-
services,
25-
`
26-
segment a() {
27-
b();
28-
}
29-
30-
segment b() {
31-
a();
32-
}
33-
`,
34-
isSdsCall,
35-
);
36-
expect(callGraphComputer.isRecursive(call)).toBeTruthy();
37-
});
38-
39-
it('should return false for non-recursive calls', async () => {
40-
const call = await getNodeOfType(
41-
services,
42-
`
43-
segment a() {
44-
b();
45-
}
46-
47-
segment b() {}
48-
`,
49-
isSdsCall,
50-
);
51-
expect(callGraphComputer.isRecursive(call)).toBeFalsy();
52-
});
53-
});
54-
5515
describe('getCallGraph', async () => {
5616
it.each(await createCallGraphTests())('$testName', async (test) => {
5717
// Test is invalid
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package tests.validation.other.expression.calls.recurcion.direct
2+
3+
segment s1() {
4+
// $TEST$ error "Call leads to infinite recursion."
5+
»s2()«;
6+
}
7+
8+
segment s2() {
9+
// $TEST$ error "Call leads to infinite recursion."
10+
»s3()«;
11+
}
12+
13+
segment s3() {
14+
// $TEST$ error "Call leads to infinite recursion."
15+
»s2()«;
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package tests.validation.other.expression.calls.recurcion.notRecursive
2+
3+
/**
4+
* Even though "s2" gets called twice, this does not lead to infinite recursion, since the 2nd time the default, empty
5+
* lambda is used for "f".
6+
*/
7+
8+
segment s1() {
9+
// $TEST$ no error "Call leads to infinite recursion."
10+
»s2(() -> s2())«;
11+
}
12+
13+
segment s2(f: () -> () = () {}) {
14+
f();
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package tests.validation.other.expression.calls.recurcion.transitiveViaFunctionPointerArgument
2+
3+
segment s1() {
4+
// $TEST$ error "Call leads to infinite recursion."
5+
»s2(s1)«;
6+
}
7+
8+
segment s2(f: () -> ()) {
9+
f();
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package tests.validation.other.expression.calls.recurcion.transitiveViaLambdaArgument
2+
3+
segment s1() {
4+
// $TEST$ error "Call leads to infinite recursion."
5+
»s2(() -> s1())«;
6+
}
7+
8+
segment s2(f: () -> ()) {
9+
f();
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package tests.validation.other.expression.calls.recurcion.transitive
2+
3+
segment s1() {
4+
// $TEST$ error "Call leads to infinite recursion."
5+
»s2()«;
6+
}
7+
8+
segment s2() {
9+
// $TEST$ error "Call leads to infinite recursion."
10+
»s3()«;
11+
}
12+
13+
segment s3() {
14+
// $TEST$ error "Call leads to infinite recursion."
15+
»s1()«;
16+
}

packages/safe-ds-lang/tests/resources/validation/skip-old/recursion.sdstest

Lines changed: 0 additions & 68 deletions
This file was deleted.

packages/safe-ds-lang/tests/resources/validation/skip-old/staticAnalysis/recursion.sdstest

Lines changed: 0 additions & 90 deletions
This file was deleted.

0 commit comments

Comments
 (0)