From e25b3e4043c129d28fe9a203803b8761ee040695 Mon Sep 17 00:00:00 2001 From: Alexis Date: Thu, 14 Nov 2024 11:14:09 +0100 Subject: [PATCH 1/9] Add detection for Recursion in Java --- .codeqlmanifest.json | 3 +- .github/workflows/test.yml | 1 + README.md | 12 ++- java/src/codeql-pack.lock.yml | 28 ++++++ .../codeql-suites/tob-java-code-scanning.qls | 5 + java/src/codeql-suites/tob-java-full.qls | 3 + java/src/docs/security/Recursion/Recursion.md | 39 ++++++++ java/src/qlpack.yml | 12 +++ java/src/security/Recursion/Recursion.qhelp | 39 ++++++++ java/src/security/Recursion/Recursion.ql | 96 +++++++++++++++++++ .../src/security/Recursion/RecursiveCall.java | 17 ++++ java/test/codeql-pack.lock.yml | 28 ++++++ java/test/qlpack.yml | 8 ++ .../security/Recursion/Recursion.expected | 3 + .../security/Recursion/Recursion.java | 49 ++++++++++ .../security/Recursion/Recursion.qlref | 1 + 16 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 java/src/codeql-pack.lock.yml create mode 100644 java/src/codeql-suites/tob-java-code-scanning.qls create mode 100644 java/src/codeql-suites/tob-java-full.qls create mode 100644 java/src/docs/security/Recursion/Recursion.md create mode 100644 java/src/qlpack.yml create mode 100644 java/src/security/Recursion/Recursion.qhelp create mode 100644 java/src/security/Recursion/Recursion.ql create mode 100644 java/src/security/Recursion/RecursiveCall.java create mode 100644 java/test/codeql-pack.lock.yml create mode 100644 java/test/qlpack.yml create mode 100644 java/test/query-tests/security/Recursion/Recursion.expected create mode 100644 java/test/query-tests/security/Recursion/Recursion.java create mode 100644 java/test/query-tests/security/Recursion/Recursion.qlref diff --git a/.codeqlmanifest.json b/.codeqlmanifest.json index 3025e97..aab4596 100644 --- a/.codeqlmanifest.json +++ b/.codeqlmanifest.json @@ -1,6 +1,7 @@ { "provide": [ "cpp/*/qlpack.yml", - "go/*/qlpack.yml" + "go/*/qlpack.yml", + "java/*/qlpack.yml" ] } \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64f84ba..458e78e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,3 +16,4 @@ jobs: run: | ${{ steps.init.outputs.codeql-path }} test run ./cpp/test/ ${{ steps.init.outputs.codeql-path }} test run ./go/test/ + ${{ steps.init.outputs.codeql-path }} test run ./java/test/ diff --git a/README.md b/README.md index 91024e9..559efba 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,14 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif - |[Missing MinVersion in tls.Config](./go/src/docs/security/MissingMinVersionTLS/MissingMinVersionTLS.md)|This rule finds cases when you do not set the `tls.Config.MinVersion` explicitly for servers. By default version 1.0 is used, which is considered insecure. This rule does not mark explicitly set insecure versions|error|medium| |[Trim functions misuse](./go/src/docs/security/TrimMisuse/TrimMisuse.md)|Finds calls to `string.{Trim,TrimLeft,TrimRight}` with the 2nd argument not being a cutset but a continuous substring to be trimmed|error|low| +### Java-kotlin + +#### Security + +| Name | Description | Severity | Precision | +| --- | ----------- | :----: | :--------: | +|[Recursive functions](./java-kotlin/src/docs/security/Recursion/Recursion.md)|Detects recursive calls|warning|low| + ## Query suites CodeQL queries are grouped into "suites". To execute queries from a specific suit add its name after a colon: `trailofbits/cpp-queries:codeql-suites/tob-cpp-full.qls`. @@ -89,7 +97,7 @@ echo "--search-path '$PWD/codeql-queries'" > "${HOME}/.config/codeql/config" Check that CodeQL CLI detects the new qlpacks: ```sh -codeql resolve qlpacks | grep trailofbits +codeql resolve packs | grep trailofbits ``` #### Before committing @@ -99,6 +107,7 @@ Run tests: cd codeql-queries codeql test run ./cpp/test codeql test run ./go/test +codeql test run ./java/test ``` Update dependencies: @@ -115,4 +124,5 @@ Generate markdown query help files ```sh codeql generate query-help ./cpp/src/ --format=markdown --output ./cpp/src/docs codeql generate query-help ./go/src/ --format=markdown --output ./go/src/docs +codeql generate query-help ./java/src/ --format=markdown --output ./java/src/docs ``` diff --git a/java/src/codeql-pack.lock.yml b/java/src/codeql-pack.lock.yml new file mode 100644 index 0000000..b1acfc0 --- /dev/null +++ b/java/src/codeql-pack.lock.yml @@ -0,0 +1,28 @@ +--- +lockVersion: 1.0.0 +dependencies: + codeql/dataflow: + version: 1.1.5 + codeql/java-all: + version: 4.2.0 + codeql/mad: + version: 1.0.11 + codeql/rangeanalysis: + version: 1.0.11 + codeql/regex: + version: 1.0.11 + codeql/ssa: + version: 1.0.11 + codeql/threat-models: + version: 1.0.11 + codeql/tutorial: + version: 1.0.11 + codeql/typeflow: + version: 1.0.11 + codeql/typetracking: + version: 1.0.11 + codeql/util: + version: 1.0.11 + codeql/xml: + version: 1.0.11 +compiled: false diff --git a/java/src/codeql-suites/tob-java-code-scanning.qls b/java/src/codeql-suites/tob-java-code-scanning.qls new file mode 100644 index 0000000..4dfa3a7 --- /dev/null +++ b/java/src/codeql-suites/tob-java-code-scanning.qls @@ -0,0 +1,5 @@ +- description: Security queries for Java +- queries: 'security' + from: trailofbits/java-queries +- exclude: + tags contain: experimental \ No newline at end of file diff --git a/java/src/codeql-suites/tob-java-full.qls b/java/src/codeql-suites/tob-java-full.qls new file mode 100644 index 0000000..3501bec --- /dev/null +++ b/java/src/codeql-suites/tob-java-full.qls @@ -0,0 +1,3 @@ +- description: Queries for Java +- queries: '.' + from: trailofbits/java-queries \ No newline at end of file diff --git a/java/src/docs/security/Recursion/Recursion.md b/java/src/docs/security/Recursion/Recursion.md new file mode 100644 index 0000000..e69e4a6 --- /dev/null +++ b/java/src/docs/security/Recursion/Recursion.md @@ -0,0 +1,39 @@ +# Recursive functions +Recursive functions are methods that call themselves either directly or indirectly through other functions. While recursion can be a powerful programming technique, unbounded recursion on user inputs can lead to stack overflow errors and program crashes, potentially enabling denial of service attacks. This query detects recursive patterns up to order 4. + + +## Recommendation +Review recursive functions and ensure that they are either: - Not processing user-controlled data - The data has been properly sanitized before recursing - The recursion has an explicit depth limit + +Consider replacing recursion with iterative alternatives where possible. + + +## Example + +```java +// From https://github.com/x-stream/xstream/blob/dfa1d35462fe84412ee72a9b0cf5b5c633086520/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamReader.java#L165 +private Token readToken() { + // ... + try { + final Token token = tokenFormatter.read(in); + switch (token.getType()) { + case Token.TYPE_MAP_ID_TO_VALUE: // 0x2 + idRegistry.put(token.getId(), token.getValue()); + return readToken(); // Next one please. + default: + return token; + } + } catch (final IOException e) { + throw new StreamException(e); + } + // ... +} +``` +In this example, a binary stream reader processes tokens recursively. + +For each new token \`0x2\`, the parser will create a new recursive call. If this stream is user-controlled, an attacker can generate too many stackframes and crash the application with a `StackOverflow` error. + + +## References +* Trail Of Bits Blog: [Low-effort denial of service with recursion](https://blog.trailofbits.com/2024/05/16/TODO/) +* CWE-674: [Uncontrolled Recursion](https://cwe.mitre.org/data/definitions/674.html) diff --git a/java/src/qlpack.yml b/java/src/qlpack.yml new file mode 100644 index 0000000..0ec638e --- /dev/null +++ b/java/src/qlpack.yml @@ -0,0 +1,12 @@ +--- +name: trailofbits/java-queries +description: CodeQL queries for Java developed by Trail of Bits +authors: Trail of Bits +version: 0.0.1 +license: AGPL +library: false +extractor: java-kotlin +dependencies: + codeql/java-all: "*" +suites: codeql-suites +defaultSuiteFile: codeql-suites/tob-java-code-scanning.qls diff --git a/java/src/security/Recursion/Recursion.qhelp b/java/src/security/Recursion/Recursion.qhelp new file mode 100644 index 0000000..4c3d956 --- /dev/null +++ b/java/src/security/Recursion/Recursion.qhelp @@ -0,0 +1,39 @@ + + + +

+ Recursive functions are methods that call themselves either directly or indirectly through other functions. + While recursion can be a powerful programming technique, unbounded recursion on user inputs can lead + to stack overflow errors and program crashes, potentially enabling denial of service attacks. + + This query detects recursive patterns up to order 4. +

+ +
+ +

+ Review recursive functions and ensure that they are either: + - Not processing user-controlled data + - The data has been properly sanitized before recursing + - The recursion has an explicit depth limit +

+

+ Consider replacing recursion with iterative alternatives where possible. +

+
+ + +

In this example, a binary stream reader processes tokens recursively.

+

For each new token `0x2`, the parser will create a new recursive call. + If this stream is user-controlled, an attacker can generate too many stackframes + and crash the application with a StackOverflow error.

+
+ +
  • + Trail Of Bits Blog: Low-effort denial of service with recursion +
  • +
  • + CWE-674: Uncontrolled Recursion +
  • +
    +
    \ No newline at end of file diff --git a/java/src/security/Recursion/Recursion.ql b/java/src/security/Recursion/Recursion.ql new file mode 100644 index 0000000..0b5ba6b --- /dev/null +++ b/java/src/security/Recursion/Recursion.ql @@ -0,0 +1,96 @@ +/** + * @name Recursive functions + * @id tob/java/recursion + * @description Detects recursive calls + * @kind problem + * @tags security + * @precision low + * @problem.severity warning + * @security-severity 3.0 + * @group security + */ + +import java + +predicate isTestPackage(RefType referenceType) { + referenceType.getPackage().getName().toLowerCase().matches("%test%") or + referenceType.getPackage().getName().toLowerCase().matches("%benchmark%") or + referenceType.getName().toLowerCase().matches("%test%") +} + +predicate isBefore(Method a, Method b) { + a.getLocation().getStartLine() < b.getLocation().getStartLine() +} + +abstract class RecursiveCall extends MethodCall { + Method m1; + Method m2; + Method m3; + Method m4; + + abstract string asString(); + + string toString(Method m) { + result = m.getName() + "(" + m.getLocation().getStartLine() + ")" + " -> " + } +} + +class RecursiveCallOrder1 extends RecursiveCall { + RecursiveCallOrder1() { + this.getMethod() = this.getEnclosingCallable() and + m1 = this.getMethod() + } + + override string asString() { result = "Recursion(1): " + toString(m1) + m1.getName() } +} + +class RecursiveCallOrder2 extends RecursiveCall { + //RecursiveCallOrder1 { + RecursiveCallOrder2() { + m1 = this.getMethod() and + m2 = m1.getACallee() and + m2.getACallee() = m1 and + isBefore(m1, m2) + } + + override string asString() { + result = "Recursion(2): " + toString(m1) + toString(m2) + m1.getName() + } +} + +class RecursiveCallOrder3 extends RecursiveCall { + RecursiveCallOrder3() { + m1 = this.getMethod() and + m2 = m1.getACallee() and + m3 = m2.getACallee() and + m3.getACallee() = m1 and + isBefore(m1, m2) and + isBefore(m1, m3) + } + + override string asString() { + result = "Recursion(3): " + toString(m1) + toString(m2) + toString(m3) + m1.getName() + } +} + +class RecursiveCallOrder4 extends RecursiveCall { + RecursiveCallOrder4() { + m1 = this.getMethod() and + m2 = m1.getACallee() and + m3 = m2.getACallee() and + m4 = m3.getACallee() and + m4.getACallee() = m1 and + isBefore(m1, m2) and + isBefore(m1, m3) and + isBefore(m1, m4) + } + + override string asString() { + result = + "Recursion(4): " + toString(m1) + toString(m2) + toString(m3) + toString(m4) + m1.getName() + } +} + +from RecursiveCall recursiveCall +where not isTestPackage(recursiveCall.getMethod().getDeclaringType()) +select recursiveCall, "$@", recursiveCall, recursiveCall.asString() diff --git a/java/src/security/Recursion/RecursiveCall.java b/java/src/security/Recursion/RecursiveCall.java new file mode 100644 index 0000000..62ad4b4 --- /dev/null +++ b/java/src/security/Recursion/RecursiveCall.java @@ -0,0 +1,17 @@ +// From https://github.com/x-stream/xstream/blob/dfa1d35462fe84412ee72a9b0cf5b5c633086520/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamReader.java#L165 +private Token readToken() { + // ... + try { + final Token token = tokenFormatter.read(in); + switch (token.getType()) { + case Token.TYPE_MAP_ID_TO_VALUE: // 0x2 + idRegistry.put(token.getId(), token.getValue()); + return readToken(); // Next one please. + default: + return token; + } + } catch (final IOException e) { + throw new StreamException(e); + } + // ... +} \ No newline at end of file diff --git a/java/test/codeql-pack.lock.yml b/java/test/codeql-pack.lock.yml new file mode 100644 index 0000000..b1acfc0 --- /dev/null +++ b/java/test/codeql-pack.lock.yml @@ -0,0 +1,28 @@ +--- +lockVersion: 1.0.0 +dependencies: + codeql/dataflow: + version: 1.1.5 + codeql/java-all: + version: 4.2.0 + codeql/mad: + version: 1.0.11 + codeql/rangeanalysis: + version: 1.0.11 + codeql/regex: + version: 1.0.11 + codeql/ssa: + version: 1.0.11 + codeql/threat-models: + version: 1.0.11 + codeql/tutorial: + version: 1.0.11 + codeql/typeflow: + version: 1.0.11 + codeql/typetracking: + version: 1.0.11 + codeql/util: + version: 1.0.11 + codeql/xml: + version: 1.0.11 +compiled: false diff --git a/java/test/qlpack.yml b/java/test/qlpack.yml new file mode 100644 index 0000000..a49c548 --- /dev/null +++ b/java/test/qlpack.yml @@ -0,0 +1,8 @@ +--- +name: trailofbits/java-tests +authors: Trail of Bits +license: AGPL +extractor: java-kotlin +tests: . +dependencies: + trailofbits/java-queries: "*" diff --git a/java/test/query-tests/security/Recursion/Recursion.expected b/java/test/query-tests/security/Recursion/Recursion.expected new file mode 100644 index 0000000..e44e7f4 --- /dev/null +++ b/java/test/query-tests/security/Recursion/Recursion.expected @@ -0,0 +1,3 @@ +| Recursion.java:8:16:8:20 | bar(...) | $@ | Recursion.java:8:16:8:20 | bar(...) | Recursion(2): bar(2) -> foo(7) -> bar | +| Recursion.java:12:16:12:32 | directRecursive(...) | $@ | Recursion.java:12:16:12:32 | directRecursive(...) | Recursion(1): directRecursive(11) -> directRecursive | +| Recursion.java:31:16:31:23 | level0(...) | $@ | Recursion.java:31:16:31:23 | level0(...) | Recursion(3): level0(15) -> level1(21) -> level2(27) -> level0 | \ No newline at end of file diff --git a/java/test/query-tests/security/Recursion/Recursion.java b/java/test/query-tests/security/Recursion/Recursion.java new file mode 100644 index 0000000..ded2f52 --- /dev/null +++ b/java/test/query-tests/security/Recursion/Recursion.java @@ -0,0 +1,49 @@ +class RecursiveCall { + public boolean bar() { + boolean fooResult = foo(); + return fooResult; + } + + public boolean foo() { + return bar(); + } + + public boolean directRecursive() { + return directRecursive(); + } + + public boolean level0() { + if (someCondition()) { + return true; + } + return level1(); + } + public boolean level1() { + if (someCondition()) { + return true; + } + return level2(); + } + public boolean level2() { + if (someCondition()) { + return true; + } + return level0(); + } + + private boolean someCondition() { + return false; + } + } + + +class NotRecursive { + + public static boolean foo() { + return bar(); + } + + public static boolean bar() { + return true; + } +} \ No newline at end of file diff --git a/java/test/query-tests/security/Recursion/Recursion.qlref b/java/test/query-tests/security/Recursion/Recursion.qlref new file mode 100644 index 0000000..3256dfa --- /dev/null +++ b/java/test/query-tests/security/Recursion/Recursion.qlref @@ -0,0 +1 @@ +security/Recursion/Recursion.ql From e4e3042e14da91dae3a7534e3429be2914322328 Mon Sep 17 00:00:00 2001 From: GrosQuildu Date: Fri, 15 Nov 2024 16:16:45 +0100 Subject: [PATCH 2/9] java recursion more tests --- .../security/Recursion/Recursion.java | 124 +++++++++++++++++- 1 file changed, 120 insertions(+), 4 deletions(-) diff --git a/java/test/query-tests/security/Recursion/Recursion.java b/java/test/query-tests/security/Recursion/Recursion.java index ded2f52..4c70b5b 100644 --- a/java/test/query-tests/security/Recursion/Recursion.java +++ b/java/test/query-tests/security/Recursion/Recursion.java @@ -1,4 +1,72 @@ -class RecursiveCall { +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +class StreamException extends RuntimeException { + public StreamException(Throwable cause) { + super(cause); + } +} + +class Token { + public static final int TYPE_MAP_ID_TO_VALUE = 0x2; + + private int type; + private String id; + private Object value; + + public int getType() { + return type; + } + + public String getId() { + return id; + } + + public Object getValue() { + return value; + } +} + +class TokenFormatter { + public Token read(InputStreamReader in) throws IOException { + // Implementation would go here + return new Token(); + } +} + +class RecursiveCallExample { + private TokenFormatter tokenFormatter = new TokenFormatter(); + private Map idRegistry = new HashMap<>(); + private InputStreamReader in; + + public RecursiveCallExample(InputStreamReader in) { + this.in = in; + } + + // finding: readToken calls itself + // Based on https://github.com/x-stream/xstream/blob/dfa1d35462fe84412ee72a9b0cf5b5c633086520/xstream/src/java/com/thoughtworks/xstream/io/binary/BinaryStreamReader.java#L165 + Token readToken() { + // ... + try { + final Token token = tokenFormatter.read(in); + switch (token.getType()) { + case Token.TYPE_MAP_ID_TO_VALUE: // 0x2 + idRegistry.put(token.getId(), token.getValue()); + return readToken(); // Next one please. + default: + return token; + } + } catch (final IOException e) { + throw new StreamException(e); + } + // ... + } +} + +class RecursiveCallBasic { + // finding: foo-bar recursive loop public boolean bar() { boolean fooResult = foo(); return fooResult; @@ -8,10 +76,12 @@ public boolean foo() { return bar(); } + // finding: calls to self public boolean directRecursive() { return directRecursive(); } + // finding: level0->level1->level2->level0 public boolean level0() { if (someCondition()) { return true; @@ -34,11 +104,57 @@ public boolean level2() { private boolean someCondition() { return false; } - } - +} -class NotRecursive { +class RecursiveCallWronglyLimited { + // finding: recursion is not limited + public boolean directRecursiveNoDepth(int anything, int depth) { + if (depth == 0) { + return true; + } + return directRecursiveNoDepth(anything, depth); + } +} + +class RecursiveCallLimited { + // todook: recursion is limited + public boolean directRecursiveDepth(int depth) { + if (depth == 0) { + return true; + } + return directRecursiveDepth(depth - 1); + } + + // todook: level0->level1->level2->level0 with bound + public boolean level0D(int depth) { + if (depth == 0) { + return true; + } + if (someCondition()) { + return true; + } + return level1D(depth-1); + } + public boolean level1D(int depth) { + if (someCondition()) { + return true; + } + return level2D(depth); + } + public boolean level2D(int depth) { + if (someCondition()) { + return true; + } + return level0D(depth-1); + } + private boolean someCondition() { + return false; + } +} + +// ok +class NotRecursive { public static boolean foo() { return bar(); } From a0e61b462158a84d15b1ed66ffef16c40d50a53c Mon Sep 17 00:00:00 2001 From: GrosQuildu Date: Fri, 15 Nov 2024 16:16:57 +0100 Subject: [PATCH 3/9] java recursion simpler query --- java/src/security/Recursion/Recursion.ql | 91 +++++------------------- 1 file changed, 16 insertions(+), 75 deletions(-) diff --git a/java/src/security/Recursion/Recursion.ql b/java/src/security/Recursion/Recursion.ql index 0b5ba6b..23bb16c 100644 --- a/java/src/security/Recursion/Recursion.ql +++ b/java/src/security/Recursion/Recursion.ql @@ -1,7 +1,7 @@ /** * @name Recursive functions - * @id tob/java/recursion - * @description Detects recursive calls + * @id tob/java/unbounded-recursion + * @description Detects possibly unbounded recursive calls * @kind problem * @tags security * @precision low @@ -18,79 +18,20 @@ predicate isTestPackage(RefType referenceType) { referenceType.getName().toLowerCase().matches("%test%") } -predicate isBefore(Method a, Method b) { - a.getLocation().getStartLine() < b.getLocation().getStartLine() -} - -abstract class RecursiveCall extends MethodCall { - Method m1; - Method m2; - Method m3; - Method m4; - - abstract string asString(); - - string toString(Method m) { - result = m.getName() + "(" + m.getLocation().getStartLine() + ")" + " -> " - } -} - -class RecursiveCallOrder1 extends RecursiveCall { - RecursiveCallOrder1() { - this.getMethod() = this.getEnclosingCallable() and - m1 = this.getMethod() - } - - override string asString() { result = "Recursion(1): " + toString(m1) + m1.getName() } -} - -class RecursiveCallOrder2 extends RecursiveCall { - //RecursiveCallOrder1 { - RecursiveCallOrder2() { - m1 = this.getMethod() and - m2 = m1.getACallee() and - m2.getACallee() = m1 and - isBefore(m1, m2) - } - - override string asString() { - result = "Recursion(2): " + toString(m1) + toString(m2) + m1.getName() +class RecursiveMethod extends Method { + RecursiveMethod() { + exists(Method m | this.calls+(m) and this = m) } } -class RecursiveCallOrder3 extends RecursiveCall { - RecursiveCallOrder3() { - m1 = this.getMethod() and - m2 = m1.getACallee() and - m3 = m2.getACallee() and - m3.getACallee() = m1 and - isBefore(m1, m2) and - isBefore(m1, m3) - } - - override string asString() { - result = "Recursion(3): " + toString(m1) + toString(m2) + toString(m3) + m1.getName() - } -} - -class RecursiveCallOrder4 extends RecursiveCall { - RecursiveCallOrder4() { - m1 = this.getMethod() and - m2 = m1.getACallee() and - m3 = m2.getACallee() and - m4 = m3.getACallee() and - m4.getACallee() = m1 and - isBefore(m1, m2) and - isBefore(m1, m3) and - isBefore(m1, m4) - } - - override string asString() { - result = - "Recursion(4): " + toString(m1) + toString(m2) + toString(m3) + toString(m4) + m1.getName() - } -} - -from RecursiveCall recursiveCall -where not isTestPackage(recursiveCall.getMethod().getDeclaringType()) -select recursiveCall, "$@", recursiveCall, recursiveCall.asString() +/** + * TODO ideas: + * - limit results to methods that take any user input + * - check if recursive calls are bounded (takes argument that is decremented for every call) + * - do not return methods that have calls to self (or unbounded recursion) that are conditional + * - gather and print whole call graph (list of calls from recursiveMethod to itself) + */ +from RecursiveMethod recursiveMethod +where + not isTestPackage(recursiveMethod.getDeclaringType()) +select recursiveMethod, "Method $@ has unbounded, possibly infinite recursive calls", recursiveMethod, recursiveMethod.toString() From c3213e4a2ee66766a163fc214a861f47650d334b Mon Sep 17 00:00:00 2001 From: GrosQuildu Date: Mon, 18 Nov 2024 14:03:24 +0100 Subject: [PATCH 4/9] update recursion query to path-problem, deduplicate results --- java/src/security/Recursion/Recursion.ql | 40 +++++++++++++++---- .../security/Recursion/Recursion.java | 33 +++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/java/src/security/Recursion/Recursion.ql b/java/src/security/Recursion/Recursion.ql index 23bb16c..c85a60f 100644 --- a/java/src/security/Recursion/Recursion.ql +++ b/java/src/security/Recursion/Recursion.ql @@ -2,7 +2,7 @@ * @name Recursive functions * @id tob/java/unbounded-recursion * @description Detects possibly unbounded recursive calls - * @kind problem + * @kind path-problem * @tags security * @precision low * @problem.severity warning @@ -12,6 +12,7 @@ import java + predicate isTestPackage(RefType referenceType) { referenceType.getPackage().getName().toLowerCase().matches("%test%") or referenceType.getPackage().getName().toLowerCase().matches("%benchmark%") or @@ -19,19 +20,44 @@ predicate isTestPackage(RefType referenceType) { } class RecursiveMethod extends Method { + Call entryCall; + Call lastCall; + RecursiveMethod() { - exists(Method m | this.calls+(m) and this = m) + not isTestPackage(this.getDeclaringType()) + and entryCall.getEnclosingCallable() = this + and edges+(entryCall, lastCall) and lastCall.getCallee() = this } + + Call getEntryCall() { result = entryCall} + + Call getLastCall() { result = lastCall} +} + +query predicate edges(Call a, Call b) { + exists(Callable c | + a.getCallee() = c and b.getCaller() = c + ) } +from RecursiveMethod recursiveMethod +where + /* for a single recursion loop we would return multiple results so we deduplicate redundant findings + returning only the one starting from a method with "greatest" name + */ + not exists(RecursiveMethod rm2 | + edges+(rm2.getEntryCall(), recursiveMethod.getLastCall()) + and exists(Call x | edges(recursiveMethod.getLastCall(), x) and edges(x, rm2.getEntryCall())) + and rm2 != recursiveMethod + and rm2.getQualifiedName() > recursiveMethod.getQualifiedName() + ) +select recursiveMethod.getLastCall(), recursiveMethod.getEntryCall(), recursiveMethod.getLastCall(), + "Found a recursion path from/to method $@", recursiveMethod, recursiveMethod.toString() + /** * TODO ideas: * - limit results to methods that take any user input * - check if recursive calls are bounded (takes argument that is decremented for every call) * - do not return methods that have calls to self (or unbounded recursion) that are conditional * - gather and print whole call graph (list of calls from recursiveMethod to itself) - */ -from RecursiveMethod recursiveMethod -where - not isTestPackage(recursiveMethod.getDeclaringType()) -select recursiveMethod, "Method $@ has unbounded, possibly infinite recursive calls", recursiveMethod, recursiveMethod.toString() + */ \ No newline at end of file diff --git a/java/test/query-tests/security/Recursion/Recursion.java b/java/test/query-tests/security/Recursion/Recursion.java index 4c70b5b..c15d52e 100644 --- a/java/test/query-tests/security/Recursion/Recursion.java +++ b/java/test/query-tests/security/Recursion/Recursion.java @@ -106,6 +106,39 @@ private boolean someCondition() { } } +class RecursiveCallNonLinear { + // finding: level0->...->level0 + public boolean level0() { + if (someOtherCondition()) { + return true; + } + if (someCondition()) { + return level1(); + } + return level2(); + } + public boolean level1() { + if (someCondition()) { + return true; + } + return level2(); + } + public boolean level2() { + if (someCondition()) { + return level1(); + } + return level0(); + } + + private boolean someCondition() { + return false; + } + + private boolean someOtherCondition() { + return true; + } +} + class RecursiveCallWronglyLimited { // finding: recursion is not limited public boolean directRecursiveNoDepth(int anything, int depth) { From c042c9d9cbb22f1717f0496eade8593a79f5fb17 Mon Sep 17 00:00:00 2001 From: Alexis Date: Mon, 18 Nov 2024 14:52:13 +0100 Subject: [PATCH 5/9] Update query to path-query --- java/src/security/Recursion/Recursion.ql | 56 +++++++++---- .../security/Recursion/Recursion.expected | 78 ++++++++++++++++++- .../security/Recursion/Recursion.java | 14 +++- 3 files changed, 130 insertions(+), 18 deletions(-) diff --git a/java/src/security/Recursion/Recursion.ql b/java/src/security/Recursion/Recursion.ql index 23bb16c..3fe3a11 100644 --- a/java/src/security/Recursion/Recursion.ql +++ b/java/src/security/Recursion/Recursion.ql @@ -2,7 +2,7 @@ * @name Recursive functions * @id tob/java/unbounded-recursion * @description Detects possibly unbounded recursive calls - * @kind problem + * @kind path-problem * @tags security * @precision low * @problem.severity warning @@ -11,6 +11,7 @@ */ import java +import semmle.code.java.dataflow.DataFlow predicate isTestPackage(RefType referenceType) { referenceType.getPackage().getName().toLowerCase().matches("%test%") or @@ -18,20 +19,49 @@ predicate isTestPackage(RefType referenceType) { referenceType.getName().toLowerCase().matches("%test%") } -class RecursiveMethod extends Method { - RecursiveMethod() { - exists(Method m | this.calls+(m) and this = m) +class RecursionSource extends MethodCall { + RecursionSource() { not isTestPackage(this.getCaller().getDeclaringType()) } + + override string toString() { + result = this.getCaller().toString() + " calls " + this.getCallee().toString() } } -/** - * TODO ideas: - * - limit results to methods that take any user input - * - check if recursive calls are bounded (takes argument that is decremented for every call) +module RecursiveConfig implements DataFlow::StateConfigSig { + class FlowState = Method; + + predicate isSource(DataFlow::Node node, FlowState state) { + node.asExpr() instanceof RecursionSource and + state = node.asExpr().(MethodCall).getCaller() + } + + predicate isSink(DataFlow::Node node, FlowState state) { + node.asExpr() instanceof RecursionSource and + state.calls+(node.asExpr().(MethodCall).getCaller()) and + node.asExpr().(MethodCall).getCallee().calls(state) + } + + predicate isBarrier(DataFlow::Node node) { + node.asExpr() instanceof MethodCall and + exists(Expr arg | arg = node.asExpr().(MethodCall).getAnArgument() | + arg instanceof BinaryExpr or + exists(BinaryExpr b | DataFlow::localFlow(DataFlow::exprNode(b), DataFlow::exprNode(arg))) + ) + } +} + +module RecursiveFlow = DataFlow::GlobalWithState; + +import RecursiveFlow::PathGraph + +/* + * TODO: This query could be improved with the following ideas: + * - limit results to methods that take any user input * - do not return methods that have calls to self (or unbounded recursion) that are conditional - * - gather and print whole call graph (list of calls from recursiveMethod to itself) + * - gather and print whole call graph (list of calls from recursiveMethod to itself) */ -from RecursiveMethod recursiveMethod -where - not isTestPackage(recursiveMethod.getDeclaringType()) -select recursiveMethod, "Method $@ has unbounded, possibly infinite recursive calls", recursiveMethod, recursiveMethod.toString() + +from RecursiveFlow::PathNode source, RecursiveFlow::PathNode sink +where RecursiveFlow::flowPath(source, sink) +// TODO(dm): de-duplicate results +select sink.getNode(), source, sink, "Found a recursion: " diff --git a/java/test/query-tests/security/Recursion/Recursion.expected b/java/test/query-tests/security/Recursion/Recursion.expected index e44e7f4..a758686 100644 --- a/java/test/query-tests/security/Recursion/Recursion.expected +++ b/java/test/query-tests/security/Recursion/Recursion.expected @@ -1,3 +1,75 @@ -| Recursion.java:8:16:8:20 | bar(...) | $@ | Recursion.java:8:16:8:20 | bar(...) | Recursion(2): bar(2) -> foo(7) -> bar | -| Recursion.java:12:16:12:32 | directRecursive(...) | $@ | Recursion.java:12:16:12:32 | directRecursive(...) | Recursion(1): directRecursive(11) -> directRecursive | -| Recursion.java:31:16:31:23 | level0(...) | $@ | Recursion.java:31:16:31:23 | level0(...) | Recursion(3): level0(15) -> level1(21) -> level2(27) -> level0 | \ No newline at end of file +edges +| Recursion.java:53:33:53:55 | readToken calls read : Token | Recursion.java:59:24:59:28 | token : Token | provenance | | +| Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken | provenance | | +| Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken : Token | provenance | | +| Recursion.java:59:24:59:28 | token : Token | Recursion.java:57:24:57:34 | readToken calls readToken | provenance | | +| Recursion.java:59:24:59:28 | token : Token | Recursion.java:57:24:57:34 | readToken calls readToken : Token | provenance | | +| Recursion.java:71:29:71:33 | bar calls foo : Boolean | Recursion.java:72:16:72:24 | fooResult : Boolean | provenance | | +| Recursion.java:71:29:71:33 | bar calls foo : Boolean | Recursion.java:72:16:72:24 | fooResult : Boolean | provenance | | +| Recursion.java:72:16:72:24 | fooResult : Boolean | Recursion.java:76:16:76:20 | foo calls bar | provenance | | +| Recursion.java:72:16:72:24 | fooResult : Boolean | Recursion.java:76:16:76:20 | foo calls bar : Boolean | provenance | | +| Recursion.java:72:16:72:24 | fooResult : Boolean | Recursion.java:76:16:76:20 | foo calls bar : Boolean | provenance | | +| Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:71:29:71:33 | bar calls foo | provenance | | +| Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:71:29:71:33 | bar calls foo : Boolean | provenance | | +| Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:71:29:71:33 | bar calls foo : Boolean | provenance | | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | provenance | | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | provenance | | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 | provenance | | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 | provenance | | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 | provenance | | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | provenance | | +| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | provenance | | +nodes +| Recursion.java:53:33:53:55 | readToken calls read : Token | semmle.label | readToken calls read : Token | +| Recursion.java:57:24:57:34 | readToken calls readToken | semmle.label | readToken calls readToken | +| Recursion.java:57:24:57:34 | readToken calls readToken : Token | semmle.label | readToken calls readToken : Token | +| Recursion.java:59:24:59:28 | token : Token | semmle.label | token : Token | +| Recursion.java:71:29:71:33 | bar calls foo | semmle.label | bar calls foo | +| Recursion.java:71:29:71:33 | bar calls foo : Boolean | semmle.label | bar calls foo : Boolean | +| Recursion.java:71:29:71:33 | bar calls foo : Boolean | semmle.label | bar calls foo : Boolean | +| Recursion.java:72:16:72:24 | fooResult : Boolean | semmle.label | fooResult : Boolean | +| Recursion.java:72:16:72:24 | fooResult : Boolean | semmle.label | fooResult : Boolean | +| Recursion.java:76:16:76:20 | foo calls bar | semmle.label | foo calls bar | +| Recursion.java:76:16:76:20 | foo calls bar : Boolean | semmle.label | foo calls bar : Boolean | +| Recursion.java:76:16:76:20 | foo calls bar : Boolean | semmle.label | foo calls bar : Boolean | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | semmle.label | directRecursive calls directRecursive | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | semmle.label | directRecursive calls directRecursive : Boolean | +| Recursion.java:89:16:89:23 | level0 calls level1 | semmle.label | level0 calls level1 | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:95:16:95:23 | level1 calls level2 | semmle.label | level1 calls level2 | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:101:16:101:23 | level2 calls level0 | semmle.label | level2 calls level0 | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth | +| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | +subpaths +#select +| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:53:33:53:55 | readToken calls read : Token | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | +| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | +| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | +| Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo | Found a recursion: | +| Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo : Boolean | Recursion.java:71:29:71:33 | bar calls foo | Found a recursion: | +| Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar | Found a recursion: | +| Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:76:16:76:20 | foo calls bar | Found a recursion: | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Found a recursion: | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Found a recursion: | +| Recursion.java:89:16:89:23 | level0 calls level1 | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 | Found a recursion: | +| Recursion.java:95:16:95:23 | level1 calls level2 | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 | Found a recursion: | +| Recursion.java:101:16:101:23 | level2 calls level0 | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | +| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | diff --git a/java/test/query-tests/security/Recursion/Recursion.java b/java/test/query-tests/security/Recursion/Recursion.java index 4c70b5b..f5414c9 100644 --- a/java/test/query-tests/security/Recursion/Recursion.java +++ b/java/test/query-tests/security/Recursion/Recursion.java @@ -117,7 +117,7 @@ public boolean directRecursiveNoDepth(int anything, int depth) { } class RecursiveCallLimited { - // todook: recursion is limited + // ok: recursion is limited public boolean directRecursiveDepth(int depth) { if (depth == 0) { return true; @@ -125,7 +125,17 @@ public boolean directRecursiveDepth(int depth) { return directRecursiveDepth(depth - 1); } - // todook: level0->level1->level2->level0 with bound + // ok: recursion is limited + public boolean directRecursiveComputedDepth(int depth) { + if (depth == 0) { + return true; + } + + int newDepth = depth - 2; + return directRecursiveComputedDepth(newDepth); + } + + // ok: level0->level1->level2->level0 with bound public boolean level0D(int depth) { if (depth == 0) { return true; From b505f25b0fb8fb454dbe4931ba78c10f0d789c49 Mon Sep 17 00:00:00 2001 From: Alexis Date: Mon, 18 Nov 2024 15:15:14 +0100 Subject: [PATCH 6/9] Update test --- .../security/Recursion/Recursion.expected | 78 +++++++++++++++++++ .../security/Recursion/Recursion.java | 33 ++++++++ 2 files changed, 111 insertions(+) diff --git a/java/test/query-tests/security/Recursion/Recursion.expected b/java/test/query-tests/security/Recursion/Recursion.expected index a758686..b34aa1c 100644 --- a/java/test/query-tests/security/Recursion/Recursion.expected +++ b/java/test/query-tests/security/Recursion/Recursion.expected @@ -28,6 +28,44 @@ edges | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | provenance | | | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | provenance | | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | provenance | | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | nodes | Recursion.java:53:33:53:55 | readToken calls read : Token | semmle.label | readToken calls read : Token | | Recursion.java:57:24:57:34 | readToken calls readToken | semmle.label | readToken calls readToken | @@ -57,6 +95,29 @@ nodes | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth | | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | +| Recursion.java:184:20:184:27 | level0 calls level1 | semmle.label | level0 calls level1 | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:186:16:186:23 | level0 calls level2 | semmle.label | level0 calls level2 | +| Recursion.java:186:16:186:23 | level0 calls level2 | semmle.label | level0 calls level2 | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | semmle.label | level0 calls level2 : Boolean | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | semmle.label | level0 calls level2 : Boolean | +| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | semmle.label | level0 calls level2 : Boolean | +| Recursion.java:192:16:192:23 | level1 calls level2 | semmle.label | level1 calls level2 | +| Recursion.java:192:16:192:23 | level1 calls level2 | semmle.label | level1 calls level2 | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:196:20:196:27 | level2 calls level1 | semmle.label | level2 calls level1 | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | semmle.label | level2 calls level1 : Boolean | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | semmle.label | level2 calls level1 : Boolean | +| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | semmle.label | level2 calls level1 : Boolean | +| Recursion.java:198:16:198:23 | level2 calls level0 | semmle.label | level2 calls level0 | +| Recursion.java:198:16:198:23 | level2 calls level0 | semmle.label | level2 calls level0 | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | subpaths #select | Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:53:33:53:55 | readToken calls read : Token | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | @@ -73,3 +134,20 @@ subpaths | Recursion.java:101:16:101:23 | level2 calls level0 | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 | Found a recursion: | | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | +| Recursion.java:184:20:184:27 | level0 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 | Found a recursion: | +| Recursion.java:184:20:184:27 | level0 calls level1 | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 | Found a recursion: | +| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | +| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | +| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | +| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | +| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | +| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | +| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | +| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | +| Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 | Found a recursion: | +| Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 | Found a recursion: | +| Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 | Found a recursion: | +| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | diff --git a/java/test/query-tests/security/Recursion/Recursion.java b/java/test/query-tests/security/Recursion/Recursion.java index f5414c9..e361b9c 100644 --- a/java/test/query-tests/security/Recursion/Recursion.java +++ b/java/test/query-tests/security/Recursion/Recursion.java @@ -172,4 +172,37 @@ public static boolean foo() { public static boolean bar() { return true; } +} + +class RecursiveCallNonLinear { + // finding: level0->...->level0 + public boolean level0() { + if (someOtherCondition()) { + return true; + } + if (someCondition()) { + return level1(); + } + return level2(); + } + public boolean level1() { + if (someCondition()) { + return true; + } + return level2(); + } + public boolean level2() { + if (someCondition()) { + return level1(); + } + return level0(); + } + + private boolean someCondition() { + return false; + } + + private boolean someOtherCondition() { + return true; + } } \ No newline at end of file From 512a6598a32a5800c4742ece26d12e67e59e1f76 Mon Sep 17 00:00:00 2001 From: Alexis Date: Tue, 19 Nov 2024 10:18:38 +0100 Subject: [PATCH 7/9] Update query --- java/src/security/Recursion/Recursion.ql | 41 +++- .../security/Recursion/Recursion.expected | 212 ++++++------------ .../security/Recursion/Recursion.java | 30 ++- 3 files changed, 121 insertions(+), 162 deletions(-) diff --git a/java/src/security/Recursion/Recursion.ql b/java/src/security/Recursion/Recursion.ql index d795b10..931c871 100644 --- a/java/src/security/Recursion/Recursion.ql +++ b/java/src/security/Recursion/Recursion.ql @@ -13,7 +13,6 @@ import java import semmle.code.java.dataflow.DataFlow - predicate isTestPackage(RefType referenceType) { referenceType.getPackage().getName().toLowerCase().matches("%test%") or referenceType.getPackage().getName().toLowerCase().matches("%benchmark%") or @@ -24,11 +23,25 @@ class RecursionSource extends MethodCall { RecursionSource() { not isTestPackage(this.getCaller().getDeclaringType()) } override string toString() { - result = this.getCaller().toString() + " calls " + this.getCallee().toString() + result = this.getCaller().toString() + " clls " + this.getCallee().toString() } } +/** + * Check if the Expr uses directly an argument of the enclosing function + */ +class ParameterOperation extends Expr { + ParameterOperation() { + this instanceof BinaryExpr or this instanceof UnaryAssignExpr + and exists( + VarAccess va | + va.getVariable() = this.getEnclosingCallable().getAParameter() | + this.getAChildExpr+() = va + ) + } +} + module RecursiveConfig implements DataFlow::StateConfigSig { class FlowState = Method; @@ -44,12 +57,26 @@ module RecursiveConfig implements DataFlow::StateConfigSig { } predicate isBarrier(DataFlow::Node node) { - node.asExpr() instanceof MethodCall and - exists(Expr arg | arg = node.asExpr().(MethodCall).getAnArgument() | - arg instanceof BinaryExpr or - exists(BinaryExpr b | DataFlow::localFlow(DataFlow::exprNode(b), DataFlow::exprNode(arg))) + exists(MethodCall ma | + ma = node.asExpr() + and ( + exists(Expr e | e = ma.getAnArgument() and e instanceof ParameterOperation) + // or exists( + // VarAccess e| + // e = ma.getAnArgument() | + // e.getVariable().getAnAssignedValue().getAChildExpr() instanceof ParameterOperation + // ) + ) ) } + + /** + * Weird but useful deduplication logic + */ + predicate isBarrierIn(DataFlow::Node node, FlowState state) { + not node.asExpr() instanceof MethodCall + or node.asExpr().(MethodCall).getCaller().getLocation().getStartLine() > state.getLocation().getStartLine() + } } module RecursiveFlow = DataFlow::GlobalWithState; @@ -66,4 +93,4 @@ import RecursiveFlow::PathGraph from RecursiveFlow::PathNode source, RecursiveFlow::PathNode sink where RecursiveFlow::flowPath(source, sink) // TODO(dm): de-duplicate results -select sink.getNode(), source, sink, "Found a recursion: " +select sink.getNode(), source, sink, "Found a recursion: " \ No newline at end of file diff --git a/java/test/query-tests/security/Recursion/Recursion.expected b/java/test/query-tests/security/Recursion/Recursion.expected index b34aa1c..61c3080 100644 --- a/java/test/query-tests/security/Recursion/Recursion.expected +++ b/java/test/query-tests/security/Recursion/Recursion.expected @@ -1,153 +1,67 @@ edges -| Recursion.java:53:33:53:55 | readToken calls read : Token | Recursion.java:59:24:59:28 | token : Token | provenance | | -| Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken | provenance | | -| Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken : Token | provenance | | -| Recursion.java:59:24:59:28 | token : Token | Recursion.java:57:24:57:34 | readToken calls readToken | provenance | | -| Recursion.java:59:24:59:28 | token : Token | Recursion.java:57:24:57:34 | readToken calls readToken : Token | provenance | | -| Recursion.java:71:29:71:33 | bar calls foo : Boolean | Recursion.java:72:16:72:24 | fooResult : Boolean | provenance | | -| Recursion.java:71:29:71:33 | bar calls foo : Boolean | Recursion.java:72:16:72:24 | fooResult : Boolean | provenance | | -| Recursion.java:72:16:72:24 | fooResult : Boolean | Recursion.java:76:16:76:20 | foo calls bar | provenance | | -| Recursion.java:72:16:72:24 | fooResult : Boolean | Recursion.java:76:16:76:20 | foo calls bar : Boolean | provenance | | -| Recursion.java:72:16:72:24 | fooResult : Boolean | Recursion.java:76:16:76:20 | foo calls bar : Boolean | provenance | | -| Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:71:29:71:33 | bar calls foo | provenance | | -| Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:71:29:71:33 | bar calls foo : Boolean | provenance | | -| Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:71:29:71:33 | bar calls foo : Boolean | provenance | | -| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | provenance | | -| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | provenance | | -| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 | provenance | | -| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 | provenance | | -| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | provenance | | -| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | provenance | | -| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | provenance | | -| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 | provenance | | -| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | provenance | | -| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | provenance | | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | provenance | | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | provenance | | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:57:24:57:34 | readToken clls readToken : Token | Recursion.java:57:24:57:34 | readToken clls readToken | provenance | | +| Recursion.java:57:24:57:34 | readToken clls readToken : Token | Recursion.java:57:24:57:34 | readToken clls readToken : Token | provenance | | +| Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | provenance | | +| Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | provenance | | +| Recursion.java:89:16:89:23 | level0 clls level1 : Boolean | Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | provenance | | +| Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | Recursion.java:89:16:89:23 | level0 clls level1 | provenance | | +| Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | Recursion.java:89:16:89:23 | level0 clls level1 : Boolean | provenance | | +| Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | provenance | | +| Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | provenance | | +| Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | provenance | | +| Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | provenance | | +| Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | provenance | | +| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 | provenance | | +| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | provenance | | +| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 | provenance | | +| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | provenance | | +| Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | provenance | | +| Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | provenance | | +| Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | provenance | | +| Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | provenance | | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | provenance | | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | provenance | | nodes -| Recursion.java:53:33:53:55 | readToken calls read : Token | semmle.label | readToken calls read : Token | -| Recursion.java:57:24:57:34 | readToken calls readToken | semmle.label | readToken calls readToken | -| Recursion.java:57:24:57:34 | readToken calls readToken : Token | semmle.label | readToken calls readToken : Token | -| Recursion.java:59:24:59:28 | token : Token | semmle.label | token : Token | -| Recursion.java:71:29:71:33 | bar calls foo | semmle.label | bar calls foo | -| Recursion.java:71:29:71:33 | bar calls foo : Boolean | semmle.label | bar calls foo : Boolean | -| Recursion.java:71:29:71:33 | bar calls foo : Boolean | semmle.label | bar calls foo : Boolean | -| Recursion.java:72:16:72:24 | fooResult : Boolean | semmle.label | fooResult : Boolean | -| Recursion.java:72:16:72:24 | fooResult : Boolean | semmle.label | fooResult : Boolean | -| Recursion.java:76:16:76:20 | foo calls bar | semmle.label | foo calls bar | -| Recursion.java:76:16:76:20 | foo calls bar : Boolean | semmle.label | foo calls bar : Boolean | -| Recursion.java:76:16:76:20 | foo calls bar : Boolean | semmle.label | foo calls bar : Boolean | -| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | semmle.label | directRecursive calls directRecursive | -| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | semmle.label | directRecursive calls directRecursive : Boolean | -| Recursion.java:89:16:89:23 | level0 calls level1 | semmle.label | level0 calls level1 | -| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | -| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | -| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | -| Recursion.java:95:16:95:23 | level1 calls level2 | semmle.label | level1 calls level2 | -| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | -| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | -| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | -| Recursion.java:101:16:101:23 | level2 calls level0 | semmle.label | level2 calls level0 | -| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | -| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | -| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | -| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth | -| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | -| Recursion.java:184:20:184:27 | level0 calls level1 | semmle.label | level0 calls level1 | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | -| Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | -| Recursion.java:186:16:186:23 | level0 calls level2 | semmle.label | level0 calls level2 | -| Recursion.java:186:16:186:23 | level0 calls level2 | semmle.label | level0 calls level2 | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | semmle.label | level0 calls level2 : Boolean | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | semmle.label | level0 calls level2 : Boolean | -| Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | semmle.label | level0 calls level2 : Boolean | -| Recursion.java:192:16:192:23 | level1 calls level2 | semmle.label | level1 calls level2 | -| Recursion.java:192:16:192:23 | level1 calls level2 | semmle.label | level1 calls level2 | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | -| Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | -| Recursion.java:196:20:196:27 | level2 calls level1 | semmle.label | level2 calls level1 | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | semmle.label | level2 calls level1 : Boolean | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | semmle.label | level2 calls level1 : Boolean | -| Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | semmle.label | level2 calls level1 : Boolean | -| Recursion.java:198:16:198:23 | level2 calls level0 | semmle.label | level2 calls level0 | -| Recursion.java:198:16:198:23 | level2 calls level0 | semmle.label | level2 calls level0 | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | -| Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:57:24:57:34 | readToken clls readToken | semmle.label | readToken clls readToken | +| Recursion.java:57:24:57:34 | readToken clls readToken : Token | semmle.label | readToken clls readToken : Token | +| Recursion.java:71:29:71:33 | bar clls foo | semmle.label | bar clls foo | +| Recursion.java:76:16:76:20 | foo clls bar | semmle.label | foo clls bar | +| Recursion.java:81:16:81:32 | directRecursive clls directRecursive | semmle.label | directRecursive clls directRecursive | +| Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | semmle.label | directRecursive clls directRecursive : Boolean | +| Recursion.java:89:16:89:23 | level0 clls level1 | semmle.label | level0 clls level1 | +| Recursion.java:89:16:89:23 | level0 clls level1 : Boolean | semmle.label | level0 clls level1 : Boolean | +| Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | semmle.label | level1 clls level2 : Boolean | +| Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | semmle.label | level2 clls level0 : Boolean | +| Recursion.java:116:20:116:27 | level0 clls level1 | semmle.label | level0 clls level1 | +| Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | semmle.label | level0 clls level1 : Boolean | +| Recursion.java:118:16:118:23 | level0 clls level2 | semmle.label | level0 clls level2 | +| Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | semmle.label | level0 clls level2 : Boolean | +| Recursion.java:124:16:124:23 | level1 clls level2 | semmle.label | level1 clls level2 | +| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | semmle.label | level1 clls level2 : Boolean | +| Recursion.java:128:20:128:27 | level2 clls level1 | semmle.label | level2 clls level1 | +| Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | semmle.label | level2 clls level1 : Boolean | +| Recursion.java:130:16:130:23 | level2 clls level0 | semmle.label | level2 clls level0 | +| Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | semmle.label | level2 clls level0 : Boolean | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | semmle.label | directRecursiveNoDepth clls directRecursiveNoDepth | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | semmle.label | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | subpaths #select -| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:53:33:53:55 | readToken calls read : Token | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | -| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | -| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | -| Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo | Found a recursion: | -| Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo : Boolean | Recursion.java:71:29:71:33 | bar calls foo | Found a recursion: | -| Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar | Found a recursion: | -| Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar : Boolean | Recursion.java:76:16:76:20 | foo calls bar | Found a recursion: | -| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Found a recursion: | -| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Found a recursion: | -| Recursion.java:89:16:89:23 | level0 calls level1 | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 | Found a recursion: | -| Recursion.java:95:16:95:23 | level1 calls level2 | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 | Found a recursion: | -| Recursion.java:101:16:101:23 | level2 calls level0 | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 | Found a recursion: | -| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | -| Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:115:16:115:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | -| Recursion.java:184:20:184:27 | level0 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 | Found a recursion: | -| Recursion.java:184:20:184:27 | level0 calls level1 | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:184:20:184:27 | level0 calls level1 | Found a recursion: | -| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | -| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | -| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | -| Recursion.java:186:16:186:23 | level0 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:186:16:186:23 | level0 calls level2 | Found a recursion: | -| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:184:20:184:27 | level0 calls level1 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | -| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:186:16:186:23 | level0 calls level2 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | -| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | -| Recursion.java:192:16:192:23 | level1 calls level2 | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:192:16:192:23 | level1 calls level2 | Found a recursion: | -| Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 | Found a recursion: | -| Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 | Found a recursion: | -| Recursion.java:196:20:196:27 | level2 calls level1 | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:196:20:196:27 | level2 calls level1 | Found a recursion: | -| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:192:16:192:23 | level1 calls level2 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | -| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:196:20:196:27 | level2 calls level1 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | -| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | -| Recursion.java:198:16:198:23 | level2 calls level0 | Recursion.java:198:16:198:23 | level2 calls level0 : Boolean | Recursion.java:198:16:198:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:57:24:57:34 | readToken clls readToken | Recursion.java:57:24:57:34 | readToken clls readToken | Recursion.java:57:24:57:34 | readToken clls readToken | Found a recursion: | +| Recursion.java:57:24:57:34 | readToken clls readToken | Recursion.java:57:24:57:34 | readToken clls readToken : Token | Recursion.java:57:24:57:34 | readToken clls readToken | Found a recursion: | +| Recursion.java:71:29:71:33 | bar clls foo | Recursion.java:71:29:71:33 | bar clls foo | Recursion.java:71:29:71:33 | bar clls foo | Found a recursion: | +| Recursion.java:76:16:76:20 | foo clls bar | Recursion.java:76:16:76:20 | foo clls bar | Recursion.java:76:16:76:20 | foo clls bar | Found a recursion: | +| Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Found a recursion: | +| Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Found a recursion: | +| Recursion.java:89:16:89:23 | level0 clls level1 | Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | Recursion.java:89:16:89:23 | level0 clls level1 | Found a recursion: | +| Recursion.java:116:20:116:27 | level0 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 | Found a recursion: | +| Recursion.java:116:20:116:27 | level0 clls level1 | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 | Found a recursion: | +| Recursion.java:118:16:118:23 | level0 clls level2 | Recursion.java:118:16:118:23 | level0 clls level2 | Recursion.java:118:16:118:23 | level0 clls level2 | Found a recursion: | +| Recursion.java:124:16:124:23 | level1 clls level2 | Recursion.java:124:16:124:23 | level1 clls level2 | Recursion.java:124:16:124:23 | level1 clls level2 | Found a recursion: | +| Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 | Found a recursion: | +| Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 | Found a recursion: | +| Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 | Found a recursion: | +| Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | Found a recursion: | +| Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:130:16:130:23 | level2 clls level0 | Found a recursion: | +| Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | Found a recursion: | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Found a recursion: | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Found a recursion: | diff --git a/java/test/query-tests/security/Recursion/Recursion.java b/java/test/query-tests/security/Recursion/Recursion.java index 7da173a..296d946 100644 --- a/java/test/query-tests/security/Recursion/Recursion.java +++ b/java/test/query-tests/security/Recursion/Recursion.java @@ -158,14 +158,32 @@ public boolean directRecursiveDepth(int depth) { return directRecursiveDepth(depth - 1); } - // ok: recursion is limited - public boolean directRecursiveComputedDepth(int depth) { + // todook: recursion is limited + // public boolean directRecursiveComputedDepth(int depth) { + // if (depth == 0) { + // return true; + // } + + // int newDepth = depth - 2; + // return directRecursiveComputedDepth(newDepth); + // } + + //finding: check is performed on a local variable + public boolean directRecLocalFlow(int depth) { + if (depth == 0) { + return true; + } + int newDepth = 2; + return directRecLocalFlow(newDepth - 1); + } + + // ok: recursion is limited with unary op + public boolean directRecUnaryDec(int depth) { if (depth == 0) { return true; } - int newDepth = depth - 2; - return directRecursiveComputedDepth(newDepth); + return directRecUnaryDec(depth--); } // ok: level0->level1->level2->level0 with bound @@ -184,11 +202,11 @@ public boolean level1D(int depth) { } return level2D(depth); } - public boolean level2D(int depth) { + public boolean level2D(int depth_value) { if (someCondition()) { return true; } - return level0D(depth-1); + return level0D(depth_value-1); } private boolean someCondition() { From 06cae621e6605bb07a3781dfe2017a62814c545b Mon Sep 17 00:00:00 2001 From: Alexis Date: Tue, 19 Nov 2024 10:19:40 +0100 Subject: [PATCH 8/9] Format query --- java/src/security/Recursion/Recursion.ql | 41 +++++++++++------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/java/src/security/Recursion/Recursion.ql b/java/src/security/Recursion/Recursion.ql index 931c871..894428e 100644 --- a/java/src/security/Recursion/Recursion.ql +++ b/java/src/security/Recursion/Recursion.ql @@ -23,9 +23,8 @@ class RecursionSource extends MethodCall { RecursionSource() { not isTestPackage(this.getCaller().getDeclaringType()) } override string toString() { - result = this.getCaller().toString() + " clls " + this.getCallee().toString() + result = this.getCaller().toString() + " calls " + this.getCallee().toString() } - } /** @@ -33,12 +32,12 @@ class RecursionSource extends MethodCall { */ class ParameterOperation extends Expr { ParameterOperation() { - this instanceof BinaryExpr or this instanceof UnaryAssignExpr - and exists( - VarAccess va | - va.getVariable() = this.getEnclosingCallable().getAParameter() | - this.getAChildExpr+() = va - ) + this instanceof BinaryExpr + or + this instanceof UnaryAssignExpr and + exists(VarAccess va | va.getVariable() = this.getEnclosingCallable().getAParameter() | + this.getAChildExpr+() = va + ) } } @@ -57,16 +56,14 @@ module RecursiveConfig implements DataFlow::StateConfigSig { } predicate isBarrier(DataFlow::Node node) { - exists(MethodCall ma | - ma = node.asExpr() - and ( - exists(Expr e | e = ma.getAnArgument() and e instanceof ParameterOperation) - // or exists( - // VarAccess e| - // e = ma.getAnArgument() | - // e.getVariable().getAnAssignedValue().getAChildExpr() instanceof ParameterOperation - // ) - ) + exists(MethodCall ma | + ma = node.asExpr() and + exists(Expr e | e = ma.getAnArgument() and e instanceof ParameterOperation) + // or exists( + // VarAccess e| + // e = ma.getAnArgument() | + // e.getVariable().getAnAssignedValue().getAChildExpr() instanceof ParameterOperation + // ) ) } @@ -74,8 +71,9 @@ module RecursiveConfig implements DataFlow::StateConfigSig { * Weird but useful deduplication logic */ predicate isBarrierIn(DataFlow::Node node, FlowState state) { - not node.asExpr() instanceof MethodCall - or node.asExpr().(MethodCall).getCaller().getLocation().getStartLine() > state.getLocation().getStartLine() + not node.asExpr() instanceof MethodCall or + node.asExpr().(MethodCall).getCaller().getLocation().getStartLine() > + state.getLocation().getStartLine() } } @@ -92,5 +90,4 @@ import RecursiveFlow::PathGraph from RecursiveFlow::PathNode source, RecursiveFlow::PathNode sink where RecursiveFlow::flowPath(source, sink) -// TODO(dm): de-duplicate results -select sink.getNode(), source, sink, "Found a recursion: " \ No newline at end of file +select sink.getNode(), source, sink, "Found a recursion: " From e930265f44eefb29f464185122ff5603ed2a1199 Mon Sep 17 00:00:00 2001 From: Alexis Date: Tue, 19 Nov 2024 14:26:51 +0100 Subject: [PATCH 9/9] Use Code Review suggestions --- java/src/security/Recursion/Recursion.ql | 14 +- .../security/Recursion/Recursion.expected | 132 +++++++++--------- 2 files changed, 75 insertions(+), 71 deletions(-) diff --git a/java/src/security/Recursion/Recursion.ql b/java/src/security/Recursion/Recursion.ql index 894428e..55891d5 100644 --- a/java/src/security/Recursion/Recursion.ql +++ b/java/src/security/Recursion/Recursion.ql @@ -32,9 +32,7 @@ class RecursionSource extends MethodCall { */ class ParameterOperation extends Expr { ParameterOperation() { - this instanceof BinaryExpr - or - this instanceof UnaryAssignExpr and + (this instanceof BinaryExpr or this instanceof UnaryAssignExpr) and exists(VarAccess va | va.getVariable() = this.getEnclosingCallable().getAParameter() | this.getAChildExpr+() = va ) @@ -44,15 +42,15 @@ class ParameterOperation extends Expr { module RecursiveConfig implements DataFlow::StateConfigSig { class FlowState = Method; - predicate isSource(DataFlow::Node node, FlowState state) { + predicate isSource(DataFlow::Node node, FlowState firstMethod) { node.asExpr() instanceof RecursionSource and - state = node.asExpr().(MethodCall).getCaller() + firstMethod = node.asExpr().(MethodCall).getCaller() } - predicate isSink(DataFlow::Node node, FlowState state) { + predicate isSink(DataFlow::Node node, FlowState firstMethod) { node.asExpr() instanceof RecursionSource and - state.calls+(node.asExpr().(MethodCall).getCaller()) and - node.asExpr().(MethodCall).getCallee().calls(state) + firstMethod.calls+(node.asExpr().(MethodCall).getCaller()) and + node.asExpr().(MethodCall).getCallee().calls(firstMethod) } predicate isBarrier(DataFlow::Node node) { diff --git a/java/test/query-tests/security/Recursion/Recursion.expected b/java/test/query-tests/security/Recursion/Recursion.expected index 61c3080..2b39bac 100644 --- a/java/test/query-tests/security/Recursion/Recursion.expected +++ b/java/test/query-tests/security/Recursion/Recursion.expected @@ -1,67 +1,73 @@ edges -| Recursion.java:57:24:57:34 | readToken clls readToken : Token | Recursion.java:57:24:57:34 | readToken clls readToken | provenance | | -| Recursion.java:57:24:57:34 | readToken clls readToken : Token | Recursion.java:57:24:57:34 | readToken clls readToken : Token | provenance | | -| Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | provenance | | -| Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | provenance | | -| Recursion.java:89:16:89:23 | level0 clls level1 : Boolean | Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | provenance | | -| Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | Recursion.java:89:16:89:23 | level0 clls level1 | provenance | | -| Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | Recursion.java:89:16:89:23 | level0 clls level1 : Boolean | provenance | | -| Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | provenance | | -| Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | provenance | | -| Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | provenance | | -| Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | provenance | | -| Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | provenance | | -| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 | provenance | | -| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | provenance | | -| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 | provenance | | -| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | provenance | | -| Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | provenance | | -| Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | provenance | | -| Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | provenance | | -| Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | provenance | | -| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | provenance | | -| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | provenance | | +| Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken | provenance | | +| Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken : Token | provenance | | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | provenance | | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | provenance | | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 | provenance | | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:116:20:116:27 | level0 calls level1 : Boolean | Recursion.java:130:16:130:23 | level2 calls level0 | provenance | | +| Recursion.java:116:20:116:27 | level0 calls level1 : Boolean | Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:118:16:118:23 | level0 calls level2 : Boolean | Recursion.java:130:16:130:23 | level2 calls level0 | provenance | | +| Recursion.java:118:16:118:23 | level0 calls level2 : Boolean | Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | provenance | | +| Recursion.java:124:16:124:23 | level1 calls level2 : Boolean | Recursion.java:116:20:116:27 | level0 calls level1 | provenance | | +| Recursion.java:124:16:124:23 | level1 calls level2 : Boolean | Recursion.java:116:20:116:27 | level0 calls level1 : Boolean | provenance | | +| Recursion.java:124:16:124:23 | level1 calls level2 : Boolean | Recursion.java:128:20:128:27 | level2 calls level1 | provenance | | +| Recursion.java:124:16:124:23 | level1 calls level2 : Boolean | Recursion.java:128:20:128:27 | level2 calls level1 : Boolean | provenance | | +| Recursion.java:128:20:128:27 | level2 calls level1 : Boolean | Recursion.java:118:16:118:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:128:20:128:27 | level2 calls level1 : Boolean | Recursion.java:124:16:124:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | Recursion.java:118:16:118:23 | level0 calls level2 : Boolean | provenance | | +| Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | Recursion.java:124:16:124:23 | level1 calls level2 : Boolean | provenance | | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth | provenance | | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | provenance | | +| Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow : Boolean | Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow | provenance | | +| Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow : Boolean | Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow : Boolean | provenance | | nodes -| Recursion.java:57:24:57:34 | readToken clls readToken | semmle.label | readToken clls readToken | -| Recursion.java:57:24:57:34 | readToken clls readToken : Token | semmle.label | readToken clls readToken : Token | -| Recursion.java:71:29:71:33 | bar clls foo | semmle.label | bar clls foo | -| Recursion.java:76:16:76:20 | foo clls bar | semmle.label | foo clls bar | -| Recursion.java:81:16:81:32 | directRecursive clls directRecursive | semmle.label | directRecursive clls directRecursive | -| Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | semmle.label | directRecursive clls directRecursive : Boolean | -| Recursion.java:89:16:89:23 | level0 clls level1 | semmle.label | level0 clls level1 | -| Recursion.java:89:16:89:23 | level0 clls level1 : Boolean | semmle.label | level0 clls level1 : Boolean | -| Recursion.java:95:16:95:23 | level1 clls level2 : Boolean | semmle.label | level1 clls level2 : Boolean | -| Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | semmle.label | level2 clls level0 : Boolean | -| Recursion.java:116:20:116:27 | level0 clls level1 | semmle.label | level0 clls level1 | -| Recursion.java:116:20:116:27 | level0 clls level1 : Boolean | semmle.label | level0 clls level1 : Boolean | -| Recursion.java:118:16:118:23 | level0 clls level2 | semmle.label | level0 clls level2 | -| Recursion.java:118:16:118:23 | level0 clls level2 : Boolean | semmle.label | level0 clls level2 : Boolean | -| Recursion.java:124:16:124:23 | level1 clls level2 | semmle.label | level1 clls level2 | -| Recursion.java:124:16:124:23 | level1 clls level2 : Boolean | semmle.label | level1 clls level2 : Boolean | -| Recursion.java:128:20:128:27 | level2 clls level1 | semmle.label | level2 clls level1 | -| Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | semmle.label | level2 clls level1 : Boolean | -| Recursion.java:130:16:130:23 | level2 clls level0 | semmle.label | level2 clls level0 | -| Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | semmle.label | level2 clls level0 : Boolean | -| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | semmle.label | directRecursiveNoDepth clls directRecursiveNoDepth | -| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | semmle.label | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | +| Recursion.java:57:24:57:34 | readToken calls readToken | semmle.label | readToken calls readToken | +| Recursion.java:57:24:57:34 | readToken calls readToken : Token | semmle.label | readToken calls readToken : Token | +| Recursion.java:71:29:71:33 | bar calls foo | semmle.label | bar calls foo | +| Recursion.java:76:16:76:20 | foo calls bar | semmle.label | foo calls bar | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | semmle.label | directRecursive calls directRecursive | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | semmle.label | directRecursive calls directRecursive : Boolean | +| Recursion.java:89:16:89:23 | level0 calls level1 | semmle.label | level0 calls level1 | +| Recursion.java:89:16:89:23 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:95:16:95:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:116:20:116:27 | level0 calls level1 | semmle.label | level0 calls level1 | +| Recursion.java:116:20:116:27 | level0 calls level1 : Boolean | semmle.label | level0 calls level1 : Boolean | +| Recursion.java:118:16:118:23 | level0 calls level2 | semmle.label | level0 calls level2 | +| Recursion.java:118:16:118:23 | level0 calls level2 : Boolean | semmle.label | level0 calls level2 : Boolean | +| Recursion.java:124:16:124:23 | level1 calls level2 | semmle.label | level1 calls level2 | +| Recursion.java:124:16:124:23 | level1 calls level2 : Boolean | semmle.label | level1 calls level2 : Boolean | +| Recursion.java:128:20:128:27 | level2 calls level1 | semmle.label | level2 calls level1 | +| Recursion.java:128:20:128:27 | level2 calls level1 : Boolean | semmle.label | level2 calls level1 : Boolean | +| Recursion.java:130:16:130:23 | level2 calls level0 | semmle.label | level2 calls level0 | +| Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | semmle.label | level2 calls level0 : Boolean | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | semmle.label | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | +| Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow | semmle.label | directRecLocalFlow calls directRecLocalFlow | +| Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow : Boolean | semmle.label | directRecLocalFlow calls directRecLocalFlow : Boolean | subpaths #select -| Recursion.java:57:24:57:34 | readToken clls readToken | Recursion.java:57:24:57:34 | readToken clls readToken | Recursion.java:57:24:57:34 | readToken clls readToken | Found a recursion: | -| Recursion.java:57:24:57:34 | readToken clls readToken | Recursion.java:57:24:57:34 | readToken clls readToken : Token | Recursion.java:57:24:57:34 | readToken clls readToken | Found a recursion: | -| Recursion.java:71:29:71:33 | bar clls foo | Recursion.java:71:29:71:33 | bar clls foo | Recursion.java:71:29:71:33 | bar clls foo | Found a recursion: | -| Recursion.java:76:16:76:20 | foo clls bar | Recursion.java:76:16:76:20 | foo clls bar | Recursion.java:76:16:76:20 | foo clls bar | Found a recursion: | -| Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Found a recursion: | -| Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Recursion.java:81:16:81:32 | directRecursive clls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive clls directRecursive | Found a recursion: | -| Recursion.java:89:16:89:23 | level0 clls level1 | Recursion.java:101:16:101:23 | level2 clls level0 : Boolean | Recursion.java:89:16:89:23 | level0 clls level1 | Found a recursion: | -| Recursion.java:116:20:116:27 | level0 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 | Found a recursion: | -| Recursion.java:116:20:116:27 | level0 clls level1 | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:116:20:116:27 | level0 clls level1 | Found a recursion: | -| Recursion.java:118:16:118:23 | level0 clls level2 | Recursion.java:118:16:118:23 | level0 clls level2 | Recursion.java:118:16:118:23 | level0 clls level2 | Found a recursion: | -| Recursion.java:124:16:124:23 | level1 clls level2 | Recursion.java:124:16:124:23 | level1 clls level2 | Recursion.java:124:16:124:23 | level1 clls level2 | Found a recursion: | -| Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 | Found a recursion: | -| Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 | Found a recursion: | -| Recursion.java:128:20:128:27 | level2 clls level1 | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:128:20:128:27 | level2 clls level1 | Found a recursion: | -| Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:128:20:128:27 | level2 clls level1 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | Found a recursion: | -| Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:130:16:130:23 | level2 clls level0 | Found a recursion: | -| Recursion.java:130:16:130:23 | level2 clls level0 | Recursion.java:130:16:130:23 | level2 clls level0 : Boolean | Recursion.java:130:16:130:23 | level2 clls level0 | Found a recursion: | -| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Found a recursion: | -| Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth clls directRecursiveNoDepth | Found a recursion: | +| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | +| Recursion.java:57:24:57:34 | readToken calls readToken | Recursion.java:57:24:57:34 | readToken calls readToken : Token | Recursion.java:57:24:57:34 | readToken calls readToken | Found a recursion: | +| Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo | Recursion.java:71:29:71:33 | bar calls foo | Found a recursion: | +| Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar | Recursion.java:76:16:76:20 | foo calls bar | Found a recursion: | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Found a recursion: | +| Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Recursion.java:81:16:81:32 | directRecursive calls directRecursive : Boolean | Recursion.java:81:16:81:32 | directRecursive calls directRecursive | Found a recursion: | +| Recursion.java:89:16:89:23 | level0 calls level1 | Recursion.java:101:16:101:23 | level2 calls level0 : Boolean | Recursion.java:89:16:89:23 | level0 calls level1 | Found a recursion: | +| Recursion.java:116:20:116:27 | level0 calls level1 | Recursion.java:128:20:128:27 | level2 calls level1 : Boolean | Recursion.java:116:20:116:27 | level0 calls level1 | Found a recursion: | +| Recursion.java:116:20:116:27 | level0 calls level1 | Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | Recursion.java:116:20:116:27 | level0 calls level1 | Found a recursion: | +| Recursion.java:118:16:118:23 | level0 calls level2 | Recursion.java:118:16:118:23 | level0 calls level2 | Recursion.java:118:16:118:23 | level0 calls level2 | Found a recursion: | +| Recursion.java:124:16:124:23 | level1 calls level2 | Recursion.java:124:16:124:23 | level1 calls level2 | Recursion.java:124:16:124:23 | level1 calls level2 | Found a recursion: | +| Recursion.java:128:20:128:27 | level2 calls level1 | Recursion.java:128:20:128:27 | level2 calls level1 | Recursion.java:128:20:128:27 | level2 calls level1 | Found a recursion: | +| Recursion.java:128:20:128:27 | level2 calls level1 | Recursion.java:128:20:128:27 | level2 calls level1 : Boolean | Recursion.java:128:20:128:27 | level2 calls level1 | Found a recursion: | +| Recursion.java:128:20:128:27 | level2 calls level1 | Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | Recursion.java:128:20:128:27 | level2 calls level1 | Found a recursion: | +| Recursion.java:130:16:130:23 | level2 calls level0 | Recursion.java:128:20:128:27 | level2 calls level1 : Boolean | Recursion.java:130:16:130:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:130:16:130:23 | level2 calls level0 | Recursion.java:130:16:130:23 | level2 calls level0 | Recursion.java:130:16:130:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:130:16:130:23 | level2 calls level0 | Recursion.java:130:16:130:23 | level2 calls level0 : Boolean | Recursion.java:130:16:130:23 | level2 calls level0 | Found a recursion: | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | +| Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth : Boolean | Recursion.java:148:16:148:54 | directRecursiveNoDepth calls directRecursiveNoDepth | Found a recursion: | +| Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow | Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow | Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow | Found a recursion: | +| Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow | Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow : Boolean | Recursion.java:177:16:177:47 | directRecLocalFlow calls directRecLocalFlow | Found a recursion: |