diff --git a/.github/workflows/swift-autobuilder.yml b/.github/workflows/swift-autobuilder.yml
new file mode 100644
index 00000000000..c10af57ce03
--- /dev/null
+++ b/.github/workflows/swift-autobuilder.yml
@@ -0,0 +1,27 @@
+name: "Swift: Build and test Xcode autobuilder"
+
+on:
+ pull_request:
+ paths:
+ - "swift/xcode-autobuilder/**"
+ - "misc/bazel/**"
+ - "*.bazel*"
+ - .github/workflows/swift-autobuilder.yml
+ branches:
+ - main
+
+jobs:
+ autobuilder:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: bazelbuild/setup-bazelisk@v2
+ - uses: actions/setup-python@v4
+ with:
+ python-version-file: 'swift/.python-version'
+ - name: Build the Xcode autobuilder
+ run: |
+ bazel build //swift/xcode-autobuilder
+ - name: Test the Xcode autobuilder
+ run: |
+ bazel test //swift/xcode-autobuilder/tests
diff --git a/.github/workflows/swift-codegen.yml b/.github/workflows/swift-codegen.yml
index 0ce0cc91c21..89e8fad7f7c 100644
--- a/.github/workflows/swift-codegen.yml
+++ b/.github/workflows/swift-codegen.yml
@@ -10,6 +10,9 @@ on:
- .github/actions/fetch-codeql/action.yml
branches:
- main
+defaults:
+ run:
+ working-directory: swift
jobs:
codegen:
@@ -18,7 +21,9 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/fetch-codeql
- uses: bazelbuild/setup-bazelisk@v2
- - uses: actions/setup-python@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version-file: 'swift/.python-version'
- uses: pre-commit/action@v3.0.0
name: Check that python code is properly formatted
with:
diff --git a/.github/workflows/swift-integration-tests.yml b/.github/workflows/swift-integration-tests.yml
index b81969f3502..a043d1b2d34 100644
--- a/.github/workflows/swift-integration-tests.yml
+++ b/.github/workflows/swift-integration-tests.yml
@@ -28,7 +28,9 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/fetch-codeql
- uses: bazelbuild/setup-bazelisk@v2
- - uses: actions/setup-python@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version-file: 'swift/.python-version'
- name: Build Swift extractor
run: |
bazel run //swift:create-extractor-pack
diff --git a/.github/workflows/swift-qltest.yml b/.github/workflows/swift-qltest.yml
index ab74e96cd57..d50e1748de0 100644
--- a/.github/workflows/swift-qltest.yml
+++ b/.github/workflows/swift-qltest.yml
@@ -33,6 +33,9 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/fetch-codeql
- uses: bazelbuild/setup-bazelisk@v2
+ - uses: actions/setup-python@v4
+ with:
+ python-version-file: 'swift/.python-version'
- name: Build Swift extractor
run: |
bazel run //swift:create-extractor-pack
diff --git a/CODEOWNERS b/CODEOWNERS
index d5ec29f0b93..6065852559b 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -20,9 +20,9 @@
/java/ql/src/semmle/code/java/dataflow/internal/tainttracking2/TaintTrackingImpl.qll @github/codeql-java @github/codeql-go
# CodeQL tools and associated docs
-/docs/codeql-cli/ @github/codeql-cli-reviewers
-/docs/codeql-for-visual-studio-code/ @github/codeql-vscode-reviewers
-/docs/ql-language-reference/ @github/codeql-frontend-reviewers
+/docs/codeql/codeql-cli/ @github/codeql-cli-reviewers
+/docs/codeql/codeql-for-visual-studio-code/ @github/codeql-vscode-reviewers
+/docs/codeql/ql-language-reference/ @github/codeql-frontend-reviewers
/docs/query-*-style-guide.md @github/codeql-analysis-reviewers
# QL for QL reviewers
diff --git a/config/identical-files.json b/config/identical-files.json
index c168f540f1e..832fac7741c 100644
--- a/config/identical-files.json
+++ b/config/identical-files.json
@@ -70,7 +70,6 @@
"python/ql/lib/semmle/python/dataflow/new/internal/tainttracking3/TaintTrackingImpl.qll",
"python/ql/lib/semmle/python/dataflow/new/internal/tainttracking4/TaintTrackingImpl.qll",
"ruby/ql/lib/codeql/ruby/dataflow/internal/tainttracking1/TaintTrackingImpl.qll",
- "ruby/ql/lib/codeql/ruby/dataflow/internal/tainttrackingforregexp/TaintTrackingImpl.qll",
"swift/ql/lib/codeql/swift/dataflow/internal/tainttracking1/TaintTrackingImpl.qll"
],
"DataFlow Java/C++/C#/Python Consistency checks": [
diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage
+If the expression after the comma operator starts at an earlier column than the expression before the comma, then
+this suspicious indentation possibly indicates a logic error, caused by a typo that may escape visual inspection.
+
+To ensure that your code is easy to read and review, use standard indentation around the comma operator. Always begin the right-hand-side operand at the same level of
+indentation (column number) as the left-hand-side operand. This makes it easier for other developers to see the intended behavior of your code.
+
+Use whitespace consistently to communicate your coding intentions. Where possible, avoid mixing tabs and spaces within a file. If you need to mix them, use them consistently.
+
+This example shows three different ways of writing the same code. The first example contains a comma instead of a semicolon which means that the final line is part of the if statement, even though the indentation suggests that it is intended to be separate. The second example looks different but is functionally the same as the first example. It is more likely that the developer intended to write the third example.
+
Check that the unused static variable does not indicate a defect, for example, an unhandled case. If the static variable is genuinuely not needed, +
Check that the unused static variable does not indicate a defect, for example, an unhandled case. If the static variable is genuinely not needed, then removing it will make code more readable. If the static variable is needed then you should update the code to fix the defect.
In the example below, the sockfd socket may remain open if an error is triggered.
-The code should be updated to ensure that the socket is always closed when when the function ends.
+The code should be updated to ensure that the socket is always closed when the function ends.
-Add comments to document the purpose of the function. In particular, ensure that the public API of the function is carefully documented. This reduces the chance that a future change to the function will introduce a defect by changing the API and breaking the expections of the calling functions. +Add comments to document the purpose of the function. In particular, ensure that the public API of the function is carefully documented. This reduces the chance that a future change to the function will introduce a defect by changing the API and breaking the expectations of the calling functions.
-This rule finds comparison expressions that use 2 or more comparison operators and are not completely paranthesized. +This rule finds comparison expressions that use 2 or more comparison operators and are not completely parenthesized. It is best to fully parenthesize complex comparison expressions to explicitly define the order of the comparison operators.
diff --git a/cpp/ql/src/Likely Bugs/ContinueInFalseLoop.ql b/cpp/ql/src/Likely Bugs/ContinueInFalseLoop.ql index 293595d60d8..5b16fc7cf8f 100644 --- a/cpp/ql/src/Likely Bugs/ContinueInFalseLoop.ql +++ b/cpp/ql/src/Likely Bugs/ContinueInFalseLoop.ql @@ -23,7 +23,7 @@ DoStmt getAFalseLoop() { /** * Gets a `do` ... `while` loop surrounding a statement. This is blocked by a * `switch` statement, since a `continue` inside a `switch` inside a loop may be - * jusitifed (`continue` breaks out of the loop whereas `break` only escapes the + * justified (`continue` breaks out of the loop whereas `break` only escapes the * `switch`). */ DoStmt enclosingLoop(Stmt s) { diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/IncorrectNotOperatorUsage.qhelp b/cpp/ql/src/Likely Bugs/Likely Typos/IncorrectNotOperatorUsage.qhelp index 37b78dd368c..bac09fe9cf1 100644 --- a/cpp/ql/src/Likely Bugs/Likely Typos/IncorrectNotOperatorUsage.qhelp +++ b/cpp/ql/src/Likely Bugs/Likely Typos/IncorrectNotOperatorUsage.qhelp @@ -6,9 +6,9 @@This rule finds logical-not operator usage as an operator for in a bit-wise operation.
-Due to the nature of logical operation result value, only the lowest bit could possibly be set, and it is unlikely to be intent in bitwise opeartions. Violations are often indicative of a typo, using a logical-not (!) opeartor instead of the bit-wise not (~) operator.
Due to the nature of logical operation result value, only the lowest bit could possibly be set, and it is unlikely to be intent in bitwise operations. Violations are often indicative of a typo, using a logical-not (!) operator instead of the bit-wise not (~) operator.
This rule is restricted to analyze bit-wise and (&) and bit-wise or (|) operation in order to provide better precision.
This rule ignores instances where a double negation (!!) is explicitly used as the opeartor of the bitwise operation, as this is a commonly used as a mechanism to normalize an integer value to either 1 or 0.
This rule ignores instances where a double negation (!!) is explicitly used as the operator of the bitwise operation, as this is a commonly used as a mechanism to normalize an integer value to either 1 or 0.
NOTE: It is not recommended to use this rule in kernel code or older C code as it will likely find several false positive instances.
While it's not the subject of this query, the expression ptr + i <
-ptr_end is also an invalid range check. It's undefined behavor in
+ptr_end is also an invalid range check. It's undefined behavior in
C/C++ to create a pointer that points more than one past the end of an
allocation.
Similarly, calls of the form strncat(dest, src, sizeof (dest) - strlen (dest)) allow one
-byte to be written ouside the dest buffer.
dest buffer.
Buffer overflows can lead to anything from a segmentation fault to a security vulnerability.
diff --git a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql index 644c48622a2..0d46332a40a 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql +++ b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql @@ -24,7 +24,7 @@ import semmle.code.cpp.valuenumbering.GlobalValueNumbering * Holds if `call` is a call to `strncat` such that `sizeArg` and `destArg` are the size and * destination arguments, respectively. */ -predicate interestringCallWithArgs(Call call, Expr sizeArg, Expr destArg) { +predicate interestingCallWithArgs(Call call, Expr sizeArg, Expr destArg) { exists(StrcatFunction strcat | strcat = call.getTarget() and sizeArg = call.getArgument(strcat.getParamSize()) and @@ -37,7 +37,7 @@ predicate interestringCallWithArgs(Call call, Expr sizeArg, Expr destArg) { * argument `destArg`, and `destArg` is the size of the buffer pointed to by `destArg`. */ predicate case1(FunctionCall fc, Expr sizeArg, VariableAccess destArg) { - interestringCallWithArgs(fc, sizeArg, destArg) and + interestingCallWithArgs(fc, sizeArg, destArg) and exists(VariableAccess va | va = sizeArg.(BufferSizeExpr).getArg() and destArg.getTarget() = va.getTarget() @@ -49,7 +49,7 @@ predicate case1(FunctionCall fc, Expr sizeArg, VariableAccess destArg) { * argument `destArg`, and `sizeArg` computes the value `sizeof (dest) - strlen (dest)`. */ predicate case2(FunctionCall fc, Expr sizeArg, VariableAccess destArg) { - interestringCallWithArgs(fc, sizeArg, destArg) and + interestingCallWithArgs(fc, sizeArg, destArg) and exists(SubExpr sub, int n | // The destination buffer is an array of size n destArg.getUnspecifiedType().(ArrayType).getSize() = n and diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.c b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.c index a4c943f556c..c386a171e6b 100644 --- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.c +++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.c @@ -1,4 +1,4 @@ -/* '#includeThis metric provides an indication of the lack of cohesion of a class, using a method proposed by Chidamber and Kemerer in 1994. The idea -behind measuring a class's cohesion is that most funcions in well-designed +behind measuring a class's cohesion is that most functions in well-designed classes will access the same fields. Types that exhibit a lack of cohesion are often trying to take on multiple responsibilities, and should be split into several smaller classes. diff --git a/cpp/ql/src/Metrics/Namespaces/StableNamespaces.qhelp b/cpp/ql/src/Metrics/Namespaces/StableNamespaces.qhelp index 44bdc327634..13eef3113da 100644 --- a/cpp/ql/src/Metrics/Namespaces/StableNamespaces.qhelp +++ b/cpp/ql/src/Metrics/Namespaces/StableNamespaces.qhelp @@ -11,7 +11,7 @@ by changes to other packages. If this metric value is high, a package is easily influenced. If the values is low, the impact of changes to other packages is likely to be minimal. Instability is estimated as the number of outgoing dependencies relative to the total - number of depencies.
+ number of dependencies.This query indicates that a call is setting the DACL field in a SECURITY_DESCRIPTOR to null.
When using SetSecurityDescriptorDacl to set a discretionary access control (DACL), setting the bDaclPresent argument to TRUE indicates the prescence of a DACL in the security description in the argument pDacl.
When using SetSecurityDescriptorDacl to set a discretionary access control (DACL), setting the bDaclPresent argument to TRUE indicates the presence of a DACL in the security description in the argument pDacl.
When the pDacl parameter does not point to a DACL (i.e. it is NULL) and the bDaclPresent flag is TRUE, a NULL DACL is specified.
A NULL DACL grants full access to any user who requests it; normal security checking is not performed with respect to the object.
The code passes user input to wordexp. This leaves the code
+vulnerable to attack by command injection, because wordexp performs command substitution.
+Command substitution is a feature that replaces $(command) or `command` with the
+output of the given command, allowing the user to run arbitrary code on the system.
+
When calling wordexp, pass the WRDE_NOCMD flag to prevent command substitution.
The following example passes a user-supplied file path to wordexp in two ways. The
+first way uses wordexp with no specified flags. As such, it is vulnerable to command
+injection.
+The second way uses wordexp with the WRDE_NOCMD flag. As such, no command substitution
+is performed, making this safe from command injection.
The first first example below is correct, as value of `i` is only read once it is checked that `scanf` has read one item. The second example is incorrect, as the return value of `scanf` is not checked, and as `scanf` might have failed to read any item before returning.
+The first example below is correct, as value of `i` is only read once it is checked that `scanf` has read one item. The second example is incorrect, as the return value of `scanf` is not checked, and as `scanf` might have failed to read any item before returning.
Some header files, such as those which define structures or classes, cannot be included more than once within a translation unit, as doing so would -cause a redefinition error. Such headers must be guarded to prevent ill-effects from multiple inclusion. Simlarly, if header files include other +cause a redefinition error. Such headers must be guarded to prevent ill-effects from multiple inclusion. Similarly, if header files include other header files, and this inclusion graph contains a cycle, then at least one file within the cycle must contain header guards in order to break the cycle. Because of cases like these, all headers should be guarded as a matter of good practice, even if they do not strictly need to be.
-Furthermore, most modern compilers contain optimisations which are triggered by header guards. If the header guard strictly conforms to the pattern +Furthermore, most modern compilers contain optimizations which are triggered by header guards. If the header guard strictly conforms to the pattern that compilers expect, then inclusions of that header other than the first have absolutely no effect: the file isn't re-read from disk, nor is it re-tokenised or re-preprocessed. This can result in a noticeable, albeit minor, improvement to compilation time.
diff --git a/cpp/ql/src/jsf/4.13 Functions/AV Rule 119.ql b/cpp/ql/src/jsf/4.13 Functions/AV Rule 119.ql index 5eef707432e..0192041dfe8 100644 --- a/cpp/ql/src/jsf/4.13 Functions/AV Rule 119.ql +++ b/cpp/ql/src/jsf/4.13 Functions/AV Rule 119.ql @@ -14,4 +14,4 @@ from Function f where f.fromSource() and f.calls+(f) -select f, "Functions shall not call theselves, either directly or indirectly." +select f, "Functions shall not call themselves, either directly or indirectly." diff --git a/cpp/ql/src/jsf/4.21 Operators/AV Rule 160.ql b/cpp/ql/src/jsf/4.21 Operators/AV Rule 160.ql index 068715dbf8f..6f22e04b9e9 100644 --- a/cpp/ql/src/jsf/4.21 Operators/AV Rule 160.ql +++ b/cpp/ql/src/jsf/4.21 Operators/AV Rule 160.ql @@ -41,4 +41,4 @@ where not ae.getParent() instanceof ExprStmt and not ae instanceof ForStmtSideEffectExpr select ae, - "AV Rule 160: An assignment expression shall be used only as the exprression in an expression statement." + "AV Rule 160: An assignment expression shall be used only as the expression in an expression statement." diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/WordexpTainted.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/WordexpTainted.expected new file mode 100644 index 00000000000..a8d7a480c81 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/WordexpTainted.expected @@ -0,0 +1,11 @@ +edges +| test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | (const char *)... | +| test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | filePath | +nodes +| test.cpp:23:20:23:23 | argv | semmle.label | argv | +| test.cpp:29:13:29:20 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:29:13:29:20 | filePath | semmle.label | filePath | +subpaths +#select +| test.cpp:29:13:29:20 | (const char *)... | test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | (const char *)... | Using user-supplied data in a `wordexp` command, without disabling command substitution, can make code vulnerable to command injection. | +| test.cpp:29:13:29:20 | filePath | test.cpp:23:20:23:23 | argv | test.cpp:29:13:29:20 | filePath | Using user-supplied data in a `wordexp` command, without disabling command substitution, can make code vulnerable to command injection. | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/WordexpTainted.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/WordexpTainted.qlref new file mode 100644 index 00000000000..ecff539f3e6 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/WordexpTainted.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-078/WordexpTainted.ql \ No newline at end of file diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/test.cpp new file mode 100644 index 00000000000..0ae98b8f163 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-078/test.cpp @@ -0,0 +1,45 @@ +#ifdef _MSC_VER +#define restrict __restrict +#else +#define restrict __restrict__ +#endif + +typedef unsigned long size_t; + +typedef struct { + size_t we_wordc; + char **we_wordv; + size_t we_offs; +} wordexp_t; + +enum { + WRDE_APPEND = (1 << 1), + WRDE_NOCMD = (1 << 2) +}; + +int wordexp(const char *restrict s, wordexp_t *restrict p, int flags); + +int main(int argc, char** argv) { + char *filePath = argv[2]; + + { + // BAD: the user string is injected directly into `wordexp` which performs command substitution + + wordexp_t we; + wordexp(filePath, &we, 0); + } + + { + // GOOD: command substitution is disabled + + wordexp_t we; + wordexp(filePath, &we, WRDE_NOCMD); + } + + { + // GOOD: command substitution is disabled + + wordexp_t we; + wordexp(filePath, &we, WRDE_NOCMD | WRDE_APPEND); + } +} diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-703/semmle/tests/FindIncorrectlyUsedExceptions.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-703/semmle/tests/FindIncorrectlyUsedExceptions.expected index af032eb387e..3bb6a86801f 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-703/semmle/tests/FindIncorrectlyUsedExceptions.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-703/semmle/tests/FindIncorrectlyUsedExceptions.expected @@ -1,3 +1,3 @@ | test.cpp:35:3:35:33 | call to runtime_error | Object creation of exception type on stack. Did you forget the throw keyword? | | test.cpp:41:3:41:11 | call to funcTest1 | There is an exception in the function that requires your attention. | -| test.cpp:42:3:42:9 | call to DllMain | DllMain contains an exeption not wrapped in a try..catch block. | +| test.cpp:42:3:42:9 | call to DllMain | DllMain contains an exception not wrapped in a try..catch block. | diff --git a/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/CommaBeforeMisleadingIndentation.expected b/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/CommaBeforeMisleadingIndentation.expected new file mode 100644 index 00000000000..e993345aa39 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/CommaBeforeMisleadingIndentation.expected @@ -0,0 +1,5 @@ +| test.cpp:49:2:49:8 | (void)... | The indentation level may be misleading for some tab sizes. | +| test.cpp:52:2:52:15 | (void)... | The indentation level may be misleading for some tab sizes. | +| test.cpp:160:3:160:9 | (void)... | The indentation level may be misleading for some tab sizes. | +| test.cpp:166:5:166:7 | ... ++ | The indentation level may be misleading for some tab sizes. | +| test.cpp:176:6:178:6 | ... ? ... : ... | The indentation level may be misleading for some tab sizes. | diff --git a/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/CommaBeforeMisleadingIndentation.qlref b/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/CommaBeforeMisleadingIndentation.qlref new file mode 100644 index 00000000000..02b5f38e358 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/CommaBeforeMisleadingIndentation.qlref @@ -0,0 +1 @@ +Best Practices/Likely Errors/CommaBeforeMisleadingIndentation.ql diff --git a/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/test.cpp b/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/test.cpp new file mode 100644 index 00000000000..dbf792db338 --- /dev/null +++ b/cpp/ql/test/query-tests/Best Practices/Likely Errors/CommaBeforeMisleadingIndentation/test.cpp @@ -0,0 +1,208 @@ +// clang-format off + +typedef unsigned size_t; + +struct X { + int foo(int y) { return y; } +} x; + +#define FOO(x) ( \ + (x), \ + (x) \ +) + +#define BAR(x, y) ((x), (y)) + +#define BAZ //printf + +struct Foo { + int i, i_array[3]; + int j; + virtual int foo(int) = 0; + virtual int bar(int, int) = 0; + int test(int (*baz)(int)); + + struct Tata { + struct Titi { + void tutu() {} + long toto() { return 42; } + } titi; + + Titi *operator->() { return &titi; } + } *tata; +}; + +int Foo::test(int (*baz)(int)) +{ + // Comma in simple if statement (prototypical example): + + if (i) + (void)i, // GOOD + j++; + + if (i) + this->foo(i), // GOOD + foo(i); + + if (i) + (void)i, // BAD + (void)j; + + if (1) FOO(i), + (void)x.foo(j); // BAD + + // Parenthesized comma (borderline example): + + foo(i++), j++; // GOOD + (foo(i++), j++); // GOOD + (foo(i++), // GOOD + j++); + (foo(i++), + foo(i++), + j++, // GOOD (?) -- Currently explicitly excluded + j++); + + x.foo(i++), j++; // GOOD + (x.foo(i++), j++); // GOOD + (x.foo(i++), // GOOD + j++); + (x.foo(i++), + x.foo(i++), + j++, // GOOD (?) -- Currently explicitly excluded + j++); + + FOO(i++), j++; // GOOD + (FOO(i++), j++); // GOOD + (FOO(i++), // GOOD + j++); + (FOO(i++), + FOO(i++), + j++, // GOOD (?) -- Currently explicitly excluded + j++); + + (void)(i++), j++; // GOOD + ((void)(i++), j++); // GOOD + ((void)(i++), // GOOD + j++); + ((void)(i++), + (void)(i++), + j++, // GOOD (?) -- Currently explicitly excluded + j++); + + // Comma in argument list doesn't count: + + bar(i++, j++); // GOOD + bar(i++, + j++); // GOOD + bar(i++ + , j++); // GOOD + bar(i++, + j++); // GOOD: common pattern and unlikely to be misread. + + BAR(i++, j++); // GOOD + BAR(i++, + j++); // GOOD + BAR(i++ + , j++); // GOOD + BAR(i++, + j++); // GOOD: common pattern and unlikely to be misread. + + using T = decltype(x.foo(i++), // GOOD + j++); + (void)sizeof(x.foo(i++), // GOOD + j++); + using U = decltype(x.foo(i++), // GOOD? Unlikely to be misread + j++); + (void)sizeof(x.foo(i++), // GOOD? Unlikely to be misread + j++); + + BAZ("%d %d\n", i, + j); // GOOD -- Currently explicitly excluded + + // Comma in loops + + while (i = foo(j++), // GOOD + i != j && i != 42 && + !foo(j)) { + i = j = i + j; + } + + while (i = foo(j++), // GOOD??? Currently ignoring loop heads + i != j && i != 42 && !foo(j)) { + i = j = i + j; + } + + for (i = 0, // GOOD? Currently ignoring loop heads. + j = 1; + i + j < 10; + i++, j++); + + for (i = 0, + j = 1; i < 10; i += 2, // GOOD? Currently ignoring loop heads. + j++) {} + + // Comma in if-conditions: + + if (i = foo(j++), + i == j) // GOOD(?) -- Currently ignoring if-conditions for the same reason as other parenthesized commas. + i = 0; + + // Mixed tabs and spaces (ugly case): + + for (i = 0, // GOOD if tab >= 4 spaces else BAD -- Currently ignoring loop heads. + j = 0; + i + j < 10; + i++, // GOOD if tab >= 4 spaces else BAD -- Currently ignoring loop heads. + j++); + + if (i) + (void)i, // GOOD if tab >= 4 spaces else BAD -- can't exclude w/o source code text :/ + (void)j; + + // LHS ends on same line RHS begins on: + + if (1) foo( + i++ + ), j++; // GOOD? [FALSE POSITIVE] + + if (1) baz( + i++ + ), j++; // GOOD... when calling a function pointer..!? + + // Weird cases: + + if (foo(j)) + return i++ + , i++ // GOOD(?) [FALSE POSITIVE] -- can't exclude w/o source code text :/ + ? 1 + : 2; + + int quux = + (tata->titi.tutu(), + foo(tata->titi.toto())); // GOOD + + (*tata)->toto(), // GOOD + i_array[i] += (int)(*tata)->toto(); + + return quux; +} + +// Comma in variadic template splice: + +namespace std { + template
Comparisons which always yield the same result are unnecessary and may indicate a bug in the
- logic. This can can happen when the data type of one of the operands has a limited range of values.
+ logic. This can happen when the data type of one of the operands has a limited range of values.
For example unsigned integers are always greater than or equal to zero, and byte
values are always less than 256.
Use speific era when creating DateTime and DateTimeOffset structs from previously stored date in Japanese calendar
+Use specific era when creating DateTime and DateTimeOffset structs from previously stored date in Japanese calendar
Don't store dates in Japanese format
Don't use hard-coded era start date for date calculations converting dates from Japanese date format
Use JapaneseCalendar class for date formatting only
Create new instances of the object that implements or has a field of type System.Security.Cryptography.ICryptoTransform to avoid sharing it accross multiple threads.
Create new instances of the object that implements or has a field of type System.Security.Cryptography.ICryptoTransform to avoid sharing it across multiple threads.
ECB should not be used as a mode for encryption. It has dangerous weaknesses. Data is encrypted the same way every time -meaning the same plaintext input will always produce the same cyphertext. This makes encrypted messages vulnerable +meaning the same plaintext input will always produce the same ciphertext. This makes encrypted messages vulnerable to replay attacks.
Fixe
-The following example shows the use of InsecureIgnoreHostKey and an insecure host key callback implemention commonly used in non-production code.
+The following example shows the use of InsecureIgnoreHostKey and an insecure host key callback implementation commonly used in non-production code.
diff --git a/go/ql/src/experimental/CWE-321/HardcodedKeys.qhelp b/go/ql/src/experimental/CWE-321/HardcodedKeys.qhelp
index b641cbda184..ddbb4572eae 100644
--- a/go/ql/src/experimental/CWE-321/HardcodedKeys.qhelp
+++ b/go/ql/src/experimental/CWE-321/HardcodedKeys.qhelp
@@ -18,7 +18,7 @@
- Generating a cryptograhically secure secret key during application initialization and using this generated key for future JWT signing requests can prevent this vulnerability.
+ Generating a cryptographically secure secret key during application initialization and using this generated key for future JWT signing requests can prevent this vulnerability.
diff --git a/go/ql/src/experimental/CWE-369/DivideByZero.qhelp b/go/ql/src/experimental/CWE-369/DivideByZero.qhelp
index ae39d1df890..dc5cb2cf205 100644
--- a/go/ql/src/experimental/CWE-369/DivideByZero.qhelp
+++ b/go/ql/src/experimental/CWE-369/DivideByZero.qhelp
@@ -18,7 +18,7 @@ possibly causing a divide-by-zero panic.
-This can be fixed by testing the divisor against against zero:
+This can be fixed by testing the divisor against zero:
diff --git a/go/ql/src/experimental/CWE-918/SSRF.qhelp b/go/ql/src/experimental/CWE-918/SSRF.qhelp
index 7eeeabb68f6..ea37350698e 100644
--- a/go/ql/src/experimental/CWE-918/SSRF.qhelp
+++ b/go/ql/src/experimental/CWE-918/SSRF.qhelp
@@ -14,7 +14,7 @@ server side request forgery attacks, where the attacker controls the request tar
To guard against server side request forgery, it is advisable to avoid putting user input directly into a
network request. If using user input is necessary, then it must be validated. It is recommended to only allow
-user input consisting of alphanumeric characters. Simply URL-encoding other chracters is not always a solution,
+user input consisting of alphanumeric characters. Simply URL-encoding other characters is not always a solution,
for example because a downstream entity that is itself vulnerable may decode again before forwarding the request.
diff --git a/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go b/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go
index cac752dbcb2..5e6bf92ddcf 100644
--- a/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go
+++ b/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go
@@ -120,7 +120,7 @@ func main() {
}
})
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
- // OK-ish: the input origin header is validated agains a whitelist.
+ // OK-ish: the input origin header is validated against a whitelist.
responseHeader := w.Header()
{
origin := req.Header.Get("origin")
@@ -137,7 +137,7 @@ func main() {
})
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
originSuffix := ".example.com"
- // OK-ish: the input origin header is validated agains a suffix.
+ // OK-ish: the input origin header is validated against a suffix.
origin := req.Header.Get("Origin")
if origin != "" && (originSuffix == "" || strings.HasSuffix(origin, originSuffix)) {
w.Header().Set("Access-Control-Allow-Origin", origin)
@@ -152,7 +152,7 @@ func main() {
})
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
originSuffix := ".example.com"
- // OK-ish: the input origin header is validated agains a whitelist.
+ // OK-ish: the input origin header is validated against a whitelist.
origin := req.Header.Get("Origin")
if origin != "" && (originSuffix == "" || AccessControlAllowOrigins[origin]) {
w.Header().Set("Access-Control-Allow-Origin", origin)
@@ -166,7 +166,7 @@ func main() {
}
})
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
- // OK-ish: the input origin header is validated agains a whitelist.
+ // OK-ish: the input origin header is validated against a whitelist.
origin := req.Header.Get("origin")
if origin != "" && origin != "null" {
if len(AccessControlAllowOrigins) == 0 || AccessControlAllowOrigins[origin] {
@@ -178,7 +178,7 @@ func main() {
}
})
// http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
- // // OK-ish: the input origin header is validated agains a whitelist.
+ // // OK-ish: the input origin header is validated against a whitelist.
// origin := req.Header.Get("origin")
// if origin != "" && origin != "null" {
// if _, ok := AccessControlAllowOrigins[origin]; ok {
@@ -190,7 +190,7 @@ func main() {
// }
// })
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
- // OK-ish: the input origin header is validated agains a whitelist.
+ // OK-ish: the input origin header is validated against a whitelist.
if origin := req.Header.Get("Origin"); cors[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else if len(origin) > 0 && cors["*"] {
@@ -202,7 +202,7 @@ func main() {
w.Header().Set("Access-Control-Allow-Credentials", "true")
})
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
- // OK-ish: the input origin header is validated agains a whitelist.
+ // OK-ish: the input origin header is validated against a whitelist.
origin := req.Header.Get("origin")
for _, v := range GetAllowOrigin() {
if v == origin {
diff --git a/go/ql/test/query-tests/Security/CWE-918/websocket.go b/go/ql/test/query-tests/Security/CWE-918/websocket.go
index db613fe5fa5..328200770ae 100644
--- a/go/ql/test/query-tests/Security/CWE-918/websocket.go
+++ b/go/ql/test/query-tests/Security/CWE-918/websocket.go
@@ -96,7 +96,7 @@ func test() {
http.HandleFunc("/ex5", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
- // good as input is tested againt regex
+ // good as input is tested against regex
if m, _ := regexp.MatchString("ws://localhost:12345/*", untrustedInput); m {
nhooyr.Dial(context.TODO(), untrustedInput, nil)
}
diff --git a/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/hasModifier.ql b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/hasModifier.ql
new file mode 100644
index 00000000000..0d1c2f3514b
--- /dev/null
+++ b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/hasModifier.ql
@@ -0,0 +1,19 @@
+class Modifier extends @modifier {
+ string toString() { none() }
+}
+
+class TypeVariable extends @typevariable {
+ string toString() { none() }
+}
+
+class Modified extends @modifiable {
+ Modified() { hasModifier(this, _) }
+
+ string toString() { none() }
+}
+
+from Modified m1, Modifier m2
+where
+ hasModifier(m1, m2) and
+ not m1 instanceof TypeVariable
+select m1, m2
diff --git a/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/modifiers.ql b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/modifiers.ql
new file mode 100644
index 00000000000..baa997f7fd8
--- /dev/null
+++ b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/modifiers.ql
@@ -0,0 +1,11 @@
+class Modifier extends @modifier {
+ string toString() { none() }
+
+ string getName() { modifiers(this, result) }
+}
+
+from Modifier m, string s
+where
+ s = m.getName() and
+ not s in ["in", "out", "reified"]
+select m, m.getName()
diff --git a/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/old.dbscheme b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/old.dbscheme
new file mode 100644
index 00000000000..709f1d1fd04
--- /dev/null
+++ b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/old.dbscheme
@@ -0,0 +1,1240 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes(
+ unique int id: @class,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @class ref
+);
+
+file_class(
+ int id: @class ref
+);
+
+class_object(
+ unique int id: @class ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @class ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isRecord(
+ unique int id: @class ref
+);
+
+interfaces(
+ unique int id: @interface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @interface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @interface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @class ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @class ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @interface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterface = @interface | @class;
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @class | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @class | @interface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar | @typevariable;
+
+@member_modifiable = @class | @interface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @class ref
+)
diff --git a/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/semmlecode.dbscheme b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/semmlecode.dbscheme
new file mode 100644
index 00000000000..ecb42310286
--- /dev/null
+++ b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/semmlecode.dbscheme
@@ -0,0 +1,1240 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes(
+ unique int id: @class,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @class ref
+);
+
+file_class(
+ int id: @class ref
+);
+
+class_object(
+ unique int id: @class ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @class ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isRecord(
+ unique int id: @class ref
+);
+
+interfaces(
+ unique int id: @interface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @interface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @interface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @class ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @class ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @interface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterface = @interface | @class;
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @class | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @class | @interface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar ;
+
+@member_modifiable = @class | @interface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @class ref
+)
diff --git a/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/upgrade.properties b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/upgrade.properties
new file mode 100644
index 00000000000..0a7ee2789f5
--- /dev/null
+++ b/java/downgrades/709f1d1fd04ffd9bbcf242f17b120f8a389949bd/upgrade.properties
@@ -0,0 +1,4 @@
+description: Remove type parameters from modifiable entities
+compatibility: backwards
+hasModifier.rel: run hasModifier.qlo
+modifiers.rel: run modifiers.qlo
diff --git a/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java b/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java
index dbe698d6759..b7b11912325 100644
--- a/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java
+++ b/java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java
@@ -4,11 +4,16 @@ import java.lang.reflect.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -29,6 +34,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes;
import com.semmle.util.concurrent.LockDirectory;
import com.semmle.util.concurrent.LockDirectory.LockingMode;
+import com.semmle.util.data.Pair;
import com.semmle.util.exception.CatastrophicError;
import com.semmle.util.exception.NestedError;
import com.semmle.util.exception.ResourceError;
@@ -43,6 +49,9 @@ import com.semmle.util.trap.dependencies.TrapSet;
import com.semmle.util.trap.pathtransformers.PathTransformer;
public class OdasaOutput {
+ // By default we use lockless TRAP writing, but this can be set
+ // if we want to use the old TRAP locking for any reason.
+ private final boolean use_trap_locking = Env.systemEnv().getBoolean("CODEQL_EXTRACTOR_JAVA_TRAP_LOCKING", false);
// either these are set ...
private final File trapFolder;
@@ -260,22 +269,59 @@ public class OdasaOutput {
* Any unique suffix needed to distinguish `sym` from other declarations with the same name.
* For functions for example, this means its parameter signature.
*/
- private TrapFileManager getMembersWriterForDecl(File trap, IrDeclaration sym, String signature) {
- TrapClassVersion currVersion = TrapClassVersion.fromSymbol(sym, log);
- String shortName = sym instanceof IrDeclarationWithName ? ((IrDeclarationWithName)sym).getName().asString() : "(name unknown)";
- if (trap.exists()) {
- // Only re-write an existing trap file if we encountered a newer version of the same class.
- TrapClassVersion trapVersion = readVersionInfo(trap);
- if (!currVersion.isValid()) {
- log.warn("Not rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap);
- } else if (currVersion.newerThan(trapVersion)) {
- log.trace("Rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap);
- deleteTrapFileAndDependencies(sym, signature);
+ private TrapFileManager getMembersWriterForDecl(File trap, File trapFileBase, TrapClassVersion trapFileVersion, IrDeclaration sym, String signature) {
+ if (use_trap_locking) {
+ TrapClassVersion currVersion = TrapClassVersion.fromSymbol(sym, log);
+ String shortName = sym instanceof IrDeclarationWithName ? ((IrDeclarationWithName)sym).getName().asString() : "(name unknown)";
+ if (trap.exists()) {
+ // Only re-write an existing trap file if we encountered a newer version of the same class.
+ TrapClassVersion trapVersion = readVersionInfo(trap);
+ if (!currVersion.isValid()) {
+ log.warn("Not rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap);
+ } else if (currVersion.newerThan(trapVersion)) {
+ log.trace("Rewriting trap file for: " + shortName + " " + trapVersion + " " + currVersion + " " + trap);
+ deleteTrapFileAndDependencies(sym, signature);
+ } else {
+ return null;
+ }
} else {
- return null;
+ log.trace("Writing trap file for: " + shortName + " " + currVersion + " " + trap);
}
} else {
- log.trace("Writing trap file for: " + shortName + " " + currVersion + " " + trap);
+ // If the TRAP file already exists then we
+ // don't need to write it.
+ if (trap.exists()) {
+ log.warn("Not rewriting trap file for " + trap.toString() + " as it exists");
+ return null;
+ }
+ // If the TRAP file was written in the past, and
+ // then renamed to its trap-old name, then we
+ // don't need to rewrite it only to rename it
+ // again.
+ File trapFileDir = trap.getParentFile();
+ File trapOld = new File(trapFileDir, trap.getName().replace(".trap.gz", ".trap-old.gz"));
+ if (trapOld.exists()) {
+ log.warn("Not rewriting trap file for " + trap.toString() + " as the trap-old exists");
+ return null;
+ }
+ // Otherwise, if any newer TRAP file has already
+ // been written then we don't need to write
+ // anything.
+ if (trapFileBase != null && trapFileVersion != null && trapFileDir.exists()) {
+ String trapFileBaseName = trapFileBase.getName();
+
+ for (File f: FileUtil.list(trapFileDir)) {
+ String name = f.getName();
+ Matcher m = selectClassVersionComponents.matcher(name);
+ if (m.matches() && m.group(1).equals(trapFileBaseName)) {
+ TrapClassVersion v = new TrapClassVersion(Integer.valueOf(m.group(2)), Integer.valueOf(m.group(3)), Long.valueOf(m.group(4)), m.group(5));
+ if (v.newerThan(trapFileVersion)) {
+ log.warn("Not rewriting trap file for " + trap.toString() + " as " + f.toString() + " exists");
+ return null;
+ }
+ }
+ }
+ }
}
return trapWriter(trap, sym, signature);
}
@@ -328,19 +374,24 @@ public class OdasaOutput {
}
writeTrapDependencies(trapDependenciesForClass);
- // Record major/minor version information for extracted class files.
- // This is subsequently used to determine whether to re-extract (a newer version of) the same class.
- File metadataFile = new File(trapFile.getAbsolutePath().replace(".trap.gz", ".metadata"));
- try {
- Map versionMap = new LinkedHashMap<>();
- TrapClassVersion tcv = TrapClassVersion.fromSymbol(sym, log);
- versionMap.put(MAJOR_VERSION, String.valueOf(tcv.getMajorVersion()));
- versionMap.put(MINOR_VERSION, String.valueOf(tcv.getMinorVersion()));
- versionMap.put(LAST_MODIFIED, String.valueOf(tcv.getLastModified()));
- versionMap.put(EXTRACTOR_NAME, tcv.getExtractorName());
- FileUtil.writePropertiesCSV(metadataFile, versionMap);
- } catch (IOException e) {
- log.warn("Could not save trap metadata file: " + metadataFile.getAbsolutePath(), e);
+
+ // If we are using TRAP locking then we
+ // need to write a metadata file.
+ if (use_trap_locking) {
+ // Record major/minor version information for extracted class files.
+ // This is subsequently used to determine whether to re-extract (a newer version of) the same class.
+ File metadataFile = new File(trapFile.getAbsolutePath().replace(".trap.gz", ".metadata"));
+ try {
+ Map versionMap = new LinkedHashMap<>();
+ TrapClassVersion tcv = TrapClassVersion.fromSymbol(sym, log);
+ versionMap.put(MAJOR_VERSION, String.valueOf(tcv.getMajorVersion()));
+ versionMap.put(MINOR_VERSION, String.valueOf(tcv.getMinorVersion()));
+ versionMap.put(LAST_MODIFIED, String.valueOf(tcv.getLastModified()));
+ versionMap.put(EXTRACTOR_NAME, tcv.getExtractorName());
+ FileUtil.writePropertiesCSV(metadataFile, versionMap);
+ } catch (IOException e) {
+ log.warn("Could not save trap metadata file: " + metadataFile.getAbsolutePath(), e);
+ }
}
}
private void writeTrapDependencies(TrapDependencies trapDependencies) {
@@ -358,6 +409,8 @@ public class OdasaOutput {
* Trap file locking.
*/
+ private final Pattern selectClassVersionComponents = Pattern.compile("(.*)#(-?[0-9]+)\\.(-?[0-9]+)-(-?[0-9]+)-(.*)\\.trap\\.gz");
+
/**
* CAUTION: to avoid the potential for deadlock between multiple concurrent extractor processes,
* only one source file {@link TrapLocker} may be open at any time, and the lock must be obtained
@@ -414,6 +467,10 @@ public class OdasaOutput {
public class TrapLocker implements AutoCloseable {
private final IrDeclaration sym;
private final File trapFile;
+ // trapFileBase is used when doing lockless TRAP file writing.
+ // It is trapFile without the #metadata.trap.gz suffix.
+ private File trapFileBase = null;
+ private TrapClassVersion trapFileVersion = null;
private final String signature;
private TrapLocker(IrDeclaration decl, String signature) {
this.sym = decl;
@@ -422,7 +479,20 @@ public class OdasaOutput {
log.error("Null symbol passed for Kotlin TRAP locker");
trapFile = null;
} else {
- trapFile = getTrapFileForDecl(sym, signature);
+ File normalTrapFile = getTrapFileForDecl(sym, signature);
+ if (use_trap_locking) {
+ trapFile = normalTrapFile;
+ } else {
+ // We encode the metadata into the filename, so that the
+ // TRAP filenames for different metadatas don't overlap.
+ trapFileVersion = TrapClassVersion.fromSymbol(sym, log);
+ String baseName = normalTrapFile.getName().replace(".trap.gz", "");
+ // If a class has lots of inner classes, then we get lots of files
+ // in a single directory. This makes our directory listings later slow.
+ // To avoid this, rather than using files named .../Foo*, we use .../Foo/Foo*.
+ trapFileBase = new File(new File(normalTrapFile.getParentFile(), baseName), baseName);
+ trapFile = new File(trapFileBase.getPath() + '#' + trapFileVersion.toString() + ".trap.gz");
+ }
}
}
private TrapLocker(File jarFile) {
@@ -437,20 +507,83 @@ public class OdasaOutput {
}
public TrapFileManager getTrapFileManager() {
if (trapFile!=null) {
- lockTrapFile(trapFile);
- return getMembersWriterForDecl(trapFile, sym, signature);
+ if (use_trap_locking) {
+ lockTrapFile(trapFile);
+ }
+ return getMembersWriterForDecl(trapFile, trapFileBase, trapFileVersion, sym, signature);
} else {
return null;
}
}
+
@Override
public void close() {
if (trapFile!=null) {
try {
- unlockTrapFile(trapFile);
+ if (use_trap_locking) {
+ unlockTrapFile(trapFile);
+ }
} catch (NestedError e) {
log.warn("Error unlocking trap file " + trapFile.getAbsolutePath(), e);
}
+
+ // If we are writing TRAP file locklessly, then now that we
+ // have finished writing our TRAP file, we want to rename
+ // and TRAP file that matches our trapFileBase but doesn't
+ // have the latest metadata.
+ // Renaming it to trap-old means that it won't be imported,
+ // but we can still use its presence to avoid future
+ // invocations rewriting it, and it means that the information
+ // is in the TRAP directory if we need it for debugging.
+ if (!use_trap_locking && sym != null) {
+ File trapFileDir = trapFileBase.getParentFile();
+ String trapFileBaseName = trapFileBase.getName();
+
+ List> pairs = new LinkedList>();
+ for (File f: FileUtil.list(trapFileDir)) {
+ String name = f.getName();
+ Matcher m = selectClassVersionComponents.matcher(name);
+ if (m.matches()) {
+ if (m.group(1).equals(trapFileBaseName)) {
+ TrapClassVersion v = new TrapClassVersion(Integer.valueOf(m.group(2)), Integer.valueOf(m.group(3)), Long.valueOf(m.group(4)), m.group(5));
+ pairs.add(new Pair(f, v));
+ } else {
+ // Everything in this directory should be for the same TRAP file base
+ log.error("Unexpected sibling " + m.group(1) + " when extracting " + trapFileBaseName);
+ }
+ }
+ }
+ if (pairs.isEmpty()) {
+ log.error("Wrote TRAP file, but no TRAP files exist for " + trapFile.getAbsolutePath());
+ } else {
+ Comparator> comparator = new Comparator>() {
+ @Override
+ public int compare(Pair p1, Pair p2) {
+ TrapClassVersion v1 = p1.snd();
+ TrapClassVersion v2 = p2.snd();
+ if (v1.equals(v2)) {
+ return 0;
+ } else if (v1.newerThan(v2)) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ };
+ TrapClassVersion latestVersion = Collections.max(pairs, comparator).snd();
+
+ for (Pair p: pairs) {
+ if (!latestVersion.equals(p.snd())) {
+ File f = p.fst();
+ File fOld = new File(f.getParentFile(), f.getName().replace(".trap.gz", ".trap-old.gz"));
+ // We aren't interested in whether or not this succeeds;
+ // it may fail because a concurrent extractor has already
+ // renamed it.
+ f.renameTo(fOld);
+ }
+ }
+ }
+ }
}
}
@@ -505,6 +638,17 @@ public class OdasaOutput {
this.lastModified = lastModified;
this.extractorName = extractorName;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof TrapClassVersion) {
+ TrapClassVersion other = (TrapClassVersion)obj;
+ return majorVersion == other.majorVersion && minorVersion == other.minorVersion && lastModified == other.lastModified && extractorName.equals(other.extractorName);
+ } else {
+ return false;
+ }
+ }
+
private boolean newerThan(TrapClassVersion tcv) {
// Classes being compiled from source have major version 0 but should take precedence
// over any classes with the same qualified name loaded from the classpath
diff --git a/java/kotlin-extractor/src/main/java/com/semmle/util/expansion/ExpansionEnvironment.java b/java/kotlin-extractor/src/main/java/com/semmle/util/expansion/ExpansionEnvironment.java
index a8008ca6299..47bbb1d2029 100644
--- a/java/kotlin-extractor/src/main/java/com/semmle/util/expansion/ExpansionEnvironment.java
+++ b/java/kotlin-extractor/src/main/java/com/semmle/util/expansion/ExpansionEnvironment.java
@@ -123,7 +123,7 @@ public class ExpansionEnvironment {
}
/**
- * This the old default constructor, which always enables command substutitions.
+ * This the old default constructor, which always enables command substitutions.
* Doing so is a security risk whenever the string you expand may come
* from an untrusted source, so you should only do that when you explicitly want
* to do it and have decided that it is safe. (And then use the constructor that
diff --git a/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java b/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java
index 6c3e754310e..81a9f46a71f 100644
--- a/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java
+++ b/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java
@@ -1033,11 +1033,11 @@ public class FileUtil
}
/**
- * Santize path string To handle windows drive letters and cross-platform builds.
+ * Sanitize path string To handle windows drive letters and cross-platform builds.
* @param pathString to be sanitized
* @return sanitized path string
*/
- private static String santizePathString(String pathString) {
+ private static String sanitizePathString(String pathString) {
// Replace ':' by '_', as the extractor does - to handle Windows drive letters
pathString = pathString.replace(':', '_');
@@ -1059,7 +1059,7 @@ public class FileUtil
*/
public static File appendAbsolutePath (File root, String absolutePath)
{
- absolutePath = santizePathString(absolutePath);
+ absolutePath = sanitizePathString(absolutePath);
return new File(root, absolutePath).getAbsoluteFile();
}
@@ -1075,7 +1075,7 @@ public class FileUtil
*/
public static Path appendAbsolutePath(Path root, String absolutePathString){
- absolutePathString = santizePathString(absolutePathString);
+ absolutePathString = sanitizePathString(absolutePathString);
Path path = Paths.get(absolutePathString);
diff --git a/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt b/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt
index 43ba9525190..9a99b05f775 100644
--- a/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt
+++ b/java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt
@@ -84,7 +84,7 @@ class ExternalDeclExtractor(val logger: FileLogger, val invocationTrapFile: Stri
// file information if needed:
val ftw = tw.makeFileTrapWriter(binaryPath, irDecl is IrClass)
- val fileExtractor = KotlinFileExtractor(logger, ftw, binaryPath, manager, this, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
+ val fileExtractor = KotlinFileExtractor(logger, ftw, null, binaryPath, manager, this, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
if (irDecl is IrClass) {
// Populate a location and compilation-unit package for the file. This is similar to
diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt b/java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt
index a4dc2aaee08..a31bfee0b4f 100644
--- a/java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt
+++ b/java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt
@@ -74,7 +74,7 @@ class KotlinExtractorExtension(
// First, if we can find our log directory, then let's try
// making a log file there:
val extractorLogDir = System.getenv("CODEQL_EXTRACTOR_JAVA_LOG_DIR")
- if (extractorLogDir != null || extractorLogDir != "") {
+ if (extractorLogDir != null && extractorLogDir != "") {
// We use a slightly different filename pattern compared
// to normal logs. Just the existence of a `-top` log is
// a sign that something's gone very wrong.
@@ -296,7 +296,9 @@ private fun doFile(
context.clear()
}
- val dbSrcFilePath = Paths.get("$dbSrcDir/$srcFilePath")
+ val srcFileRelativePath = srcFilePath.replace(':', '_')
+
+ val dbSrcFilePath = Paths.get("$dbSrcDir/$srcFileRelativePath")
val dbSrcDirPath = dbSrcFilePath.parent
Files.createDirectories(dbSrcDirPath)
val srcTmpFile = File.createTempFile(dbSrcFilePath.fileName.toString() + ".", ".src.tmp", dbSrcDirPath.toFile())
@@ -305,7 +307,7 @@ private fun doFile(
}
srcTmpFile.renameTo(dbSrcFilePath.toFile())
- val trapFileName = "$dbTrapDir/$srcFilePath.trap"
+ val trapFileName = "$dbTrapDir/$srcFileRelativePath.trap"
val trapFileWriter = getTrapFileWriter(compression, logger, trapFileName)
if (checkTrapIdentical || !trapFileWriter.exists()) {
@@ -322,7 +324,8 @@ private fun doFile(
// file information
val sftw = tw.makeSourceFileTrapWriter(srcFile, true)
val externalDeclExtractor = ExternalDeclExtractor(logger, invocationTrapFile, srcFilePath, primitiveTypeMapping, pluginContext, globalExtensionState, fileTrapWriter)
- val fileExtractor = KotlinFileExtractor(logger, sftw, srcFilePath, null, externalDeclExtractor, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
+ val linesOfCode = LinesOfCode(logger, sftw, srcFile)
+ val fileExtractor = KotlinFileExtractor(logger, sftw, linesOfCode, srcFilePath, null, externalDeclExtractor, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
fileExtractor.extractFileContents(srcFile, sftw.fileId)
externalDeclExtractor.extractExternalClasses()
@@ -397,7 +400,7 @@ private abstract class TrapFileWriter(val logger: FileLogger, trapName: String,
fun getTempWriter(): BufferedWriter {
if (this::tempFile.isInitialized) {
- logger.error("Temp writer reinitiailised for $realFile")
+ logger.error("Temp writer reinitialized for $realFile")
}
tempFile = File.createTempFile(realFile.getName() + ".", ".trap.tmp" + extension, parentDir)
return getWriter(tempFile)
diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt
index 1c34f1d8471..5741aab36d1 100644
--- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt
+++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt
@@ -36,6 +36,7 @@ import kotlin.collections.ArrayList
open class KotlinFileExtractor(
override val logger: FileLogger,
override val tw: FileTrapWriter,
+ val linesOfCode: LinesOfCode?,
val filePath: String,
dependencyCollector: OdasaOutput.TrapFileManager?,
externalClassExtractor: ExternalDeclExtractor,
@@ -91,6 +92,8 @@ open class KotlinFileExtractor(
if (!declarationStack.isEmpty()) {
logger.errorElement("Declaration stack is not empty after processing the file", file)
}
+
+ linesOfCode?.linesOfCodeInFile(id)
}
}
@@ -117,11 +120,7 @@ open class KotlinFileExtractor(
}
private fun shouldExtractDecl(declaration: IrDeclaration, extractPrivateMembers: Boolean) =
- extractPrivateMembers ||
- when(declaration) {
- is IrDeclarationWithVisibility -> declaration.visibility.let { it != DescriptorVisibilities.PRIVATE && it != DescriptorVisibilities.PRIVATE_TO_THIS }
- else -> true
- }
+ extractPrivateMembers || !isPrivate(declaration)
fun extractDeclaration(declaration: IrDeclaration, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean) {
with("declaration", declaration) {
@@ -203,6 +202,16 @@ open class KotlinFileExtractor(
}
}
+ if (tp.isReified) {
+ addModifiers(id, "reified")
+ }
+
+ if (tp.variance == Variance.IN_VARIANCE) {
+ addModifiers(id, "in")
+ } else if (tp.variance == Variance.OUT_VARIANCE) {
+ addModifiers(id, "out")
+ }
+
return id
}
}
@@ -231,7 +240,7 @@ open class KotlinFileExtractor(
// default java visibility (top level)
}
JavaVisibilities.ProtectedAndPackage -> {
- // default java visibility (member level)
+ addModifiers(id, "protected")
}
else -> logger.errorElement("Unexpected delegated visibility: $v", elementForLocation)
}
@@ -354,23 +363,37 @@ open class KotlinFileExtractor(
}
}
+ private fun makeTypeParamSubstitution(c: IrClass, argsIncludingOuterClasses: List?) =
+ when (argsIncludingOuterClasses) {
+ null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() }
+ else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses)
+ }
+
+ fun extractDeclarationPrototype(d: IrDeclaration, parentId: Label, argsIncludingOuterClasses: List?, typeParamSubstitutionQ: TypeSubstitution? = null) {
+ val typeParamSubstitution = typeParamSubstitutionQ ?:
+ when(val parent = d.parent) {
+ is IrClass -> makeTypeParamSubstitution(parent, argsIncludingOuterClasses)
+ else -> {
+ logger.warnElement("Unable to extract prototype of local declaration", d)
+ return
+ }
+ }
+ when (d) {
+ is IrFunction -> extractFunction(d, parentId, extractBody = false, extractMethodAndParameterTypeAccesses = false, typeParamSubstitution, argsIncludingOuterClasses)
+ is IrProperty -> extractProperty(d, parentId, extractBackingField = false, extractFunctionBodies = false, extractPrivateMembers = false, typeParamSubstitution, argsIncludingOuterClasses)
+ else -> {}
+ }
+ }
+
// `argsIncludingOuterClasses` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
private fun extractNonPrivateMemberPrototypes(c: IrClass, argsIncludingOuterClasses: List?, id: Label) {
with("member prototypes", c) {
- val typeParamSubstitution =
- when (argsIncludingOuterClasses) {
- null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() }
- else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses)
- }
+ val typeParamSubstitution = makeTypeParamSubstitution(c, argsIncludingOuterClasses)
c.declarations.map {
if (shouldExtractDecl(it, false)) {
- when(it) {
- is IrFunction -> extractFunction(it, id, extractBody = false, extractMethodAndParameterTypeAccesses = false, typeParamSubstitution, argsIncludingOuterClasses)
- is IrProperty -> extractProperty(it, id, extractBackingField = false, extractFunctionBodies = false, extractPrivateMembers = false, typeParamSubstitution, argsIncludingOuterClasses)
- else -> {}
- }
+ extractDeclarationPrototype(it, id, argsIncludingOuterClasses, typeParamSubstitution)
}
}
}
@@ -472,6 +495,8 @@ open class KotlinFileExtractor(
extractClassModifiers(c, id)
extractClassSupertypes(c, id, inReceiverContext = true) // inReceiverContext = true is specified to force extraction of member prototypes of base types
+ linesOfCode?.linesOfCodeInDeclaration(c, id)
+
return id
}
}
@@ -568,12 +593,7 @@ open class KotlinFileExtractor(
var parent: IrDeclarationParent? = declarationParent
while (parent != null) {
if (parent is IrClass) {
- val parentId =
- if (parent.isAnonymousObject) {
- useAnonymousClass(parent).javaResult.id.cast()
- } else {
- useClassInstance(parent, parentClassTypeArguments).typeResult.id
- }
+ val parentId = useClassInstance(parent, parentClassTypeArguments).typeResult.id
tw.writeEnclInReftype(innerId, parentId)
if (innerClass != null && innerClass.isCompanion) {
// If we are a companion then our parent has a
@@ -718,6 +738,8 @@ open class KotlinFileExtractor(
val locId = tw.getWholeFileLocation()
tw.writeHasLocation(clinitId, locId)
+ addModifiers(clinitId, "static")
+
// add and return body block:
Pair(extractBlockBody(clinitId, locId), clinitId)
}
@@ -851,7 +873,7 @@ open class KotlinFileExtractor(
extractTypeAccess(useType(paramType), locId, paramId, -1)
}
}
- val paramsSignature = allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature }
+ val paramsSignature = allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) }
val shortName = getDefaultsMethodName(f)
if (f.symbol is IrConstructorSymbol) {
@@ -1061,6 +1083,14 @@ open class KotlinFileExtractor(
}
}
+ private fun signatureOrWarn(t: TypeResult<*>, associatedElement: IrElement?) =
+ t.signature ?: "".also {
+ if (associatedElement != null)
+ logger.warnElement("Needed a signature for a type that doesn't have one", associatedElement)
+ else
+ logger.warn("Needed a signature for a type that doesn't have one")
+ }
+
private fun forceExtractFunction(f: IrFunction, parentId: Label, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List?, extractOrigin: Boolean = true, overriddenAttributes: OverriddenFunctionAttributes? = null): Label {
with("function", f) {
DeclarationStackAdjuster(f, overriddenAttributes).use {
@@ -1097,7 +1127,7 @@ open class KotlinFileExtractor(
paramTypes
}
- val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature }
+ val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) }
val adjustedReturnType = addJavaLoweringWildcards(getAdjustedReturnType(f), false, (javaCallable as? JavaMethod)?.returnType)
val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType
@@ -1148,6 +1178,8 @@ open class KotlinFileExtractor(
addModifiers(id, "suspend")
}
+ linesOfCode?.linesOfCodeInDeclaration(f, id)
+
return id
}
}
@@ -1251,6 +1283,10 @@ open class KotlinFileExtractor(
}
extractVisibility(p, id, p.visibility)
+
+ if (p.isLateinit) {
+ addModifiers(id, "lateinit")
+ }
}
}
}
@@ -1406,6 +1442,9 @@ open class KotlinFileExtractor(
if (!v.isVar) {
addModifiers(varId, "final")
}
+ if (v.isLateinit) {
+ addModifiers(varId, "lateinit")
+ }
}
}
@@ -1729,7 +1768,7 @@ open class KotlinFileExtractor(
private fun extractsDefaultsCall(
syntacticCallTarget: IrFunction,
locId: Label,
- callsite: IrCall,
+ resultType: IrType,
enclosingCallable: Label,
callsiteParent: Label,
childIdx: Int,
@@ -1744,7 +1783,7 @@ open class KotlinFileExtractor(
useFunction(callTarget)
}
val defaultMethodLabel = getDefaultsMethodLabel(callTarget)
- val id = extractMethodAccessWithoutArgs(callsite.type, locId, enclosingCallable, callsiteParent, childIdx, enclosingStmt, defaultMethodLabel)
+ val id = extractMethodAccessWithoutArgs(resultType, locId, enclosingCallable, callsiteParent, childIdx, enclosingStmt, defaultMethodLabel)
if (callTarget.isLocalFunction()) {
extractTypeAccess(getLocallyVisibleFunctionLabels(callTarget).type, locId, id, -1, enclosingCallable, enclosingStmt)
@@ -1847,7 +1886,8 @@ open class KotlinFileExtractor(
fun extractRawMethodAccess(
syntacticCallTarget: IrFunction,
- callsite: IrCall,
+ locElement: IrElement,
+ resultType: IrType,
enclosingCallable: Label,
callsiteParent: Label,
childIdx: Int,
@@ -1859,13 +1899,13 @@ open class KotlinFileExtractor(
extractClassTypeArguments: Boolean = false,
superQualifierSymbol: IrClassSymbol? = null) {
- val locId = tw.getLocation(callsite)
+ val locId = tw.getLocation(locElement)
if (valueArguments.any { it == null }) {
extractsDefaultsCall(
syntacticCallTarget,
locId,
- callsite,
+ resultType,
enclosingCallable,
callsiteParent,
childIdx,
@@ -1878,7 +1918,7 @@ open class KotlinFileExtractor(
extractRawMethodAccess(
syntacticCallTarget,
locId,
- callsite.type,
+ resultType,
enclosingCallable,
callsiteParent,
childIdx,
@@ -2209,7 +2249,7 @@ open class KotlinFileExtractor(
return
}
- extractRawMethodAccess(syntacticCallTarget, c, callable, parent, idx, enclosingStmt, (0 until c.valueArgumentsCount).map { c.getValueArgument(it) }, c.dispatchReceiver, c.extensionReceiver, typeArgs, extractClassTypeArguments, c.superQualifierSymbol)
+ extractRawMethodAccess(syntacticCallTarget, c, c.type, callable, parent, idx, enclosingStmt, (0 until c.valueArgumentsCount).map { c.getValueArgument(it) }, c.dispatchReceiver, c.extensionReceiver, typeArgs, extractClassTypeArguments, c.superQualifierSymbol)
}
fun extractSpecialEnumFunction(fnName: String){
@@ -2313,7 +2353,7 @@ open class KotlinFileExtractor(
}
isFunction(target, "kotlin", "String", "plus", true) -> {
findJdkIntrinsicOrWarn("stringPlus", c)?.let { stringPlusFn ->
- extractRawMethodAccess(stringPlusFn, c, callable, parent, idx, enclosingStmt, listOf(c.extensionReceiver, c.getValueArgument(0)), null, null)
+ extractRawMethodAccess(stringPlusFn, c, c.type, callable, parent, idx, enclosingStmt, listOf(c.extensionReceiver, c.getValueArgument(0)), null, null)
}
}
isNumericFunction(target, listOf("plus", "minus", "times", "div", "rem", "and", "or", "xor", "shl", "shr", "ushr")) -> {
@@ -2435,7 +2475,10 @@ open class KotlinFileExtractor(
}
}
tw.writeExprsKotlinType(id, type.kotlinResult.id)
- unaryopDisp(id)
+ if (isFunction(target, "kotlin", "Byte or Short", { it == "Byte" || it == "Short" }, "inv"))
+ unaryopReceiver(id, c.extensionReceiver, "Extension receiver")
+ else
+ unaryopDisp(id)
}
// We need to handle all the builtin operators defines in BuiltInOperatorNames in
// compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/IrBuiltIns.kt
@@ -2555,7 +2598,7 @@ open class KotlinFileExtractor(
}
isFunction(target, "kotlin", "Any", "toString", true) -> {
stringValueOfObjectMethod?.let {
- extractRawMethodAccess(it, c, callable, parent, idx, enclosingStmt, listOf(c.extensionReceiver), null, null)
+ extractRawMethodAccess(it, c, c.type, callable, parent, idx, enclosingStmt, listOf(c.extensionReceiver), null, null)
}
}
isBuiltinCallKotlin(c, "enumValues") -> {
@@ -2605,6 +2648,24 @@ open class KotlinFileExtractor(
|| isBuiltinCallKotlin(c, "byteArrayOf")
|| isBuiltinCallKotlin(c, "booleanArrayOf") -> {
+
+ val isPrimitiveArrayCreation = !isBuiltinCallKotlin(c, "arrayOf")
+ val elementType = if (isPrimitiveArrayCreation) {
+ c.type.getArrayElementType(pluginContext.irBuiltIns)
+ } else {
+ // TODO: is there any reason not to always use getArrayElementType?
+ if (c.typeArgumentsCount == 1) {
+ c.getTypeArgument(0).also {
+ if (it == null) {
+ logger.errorElement("Type argument missing in an arrayOf call", c)
+ }
+ }
+ } else {
+ logger.errorElement("Expected to find one type argument in arrayOf call", c)
+ null
+ }
+ }
+
val arg = if (c.valueArgumentsCount == 1) c.getValueArgument(0) else {
logger.errorElement("Expected to find only one (vararg) argument in ${c.symbol.owner.name.asString()} call", c)
null
@@ -2615,59 +2676,7 @@ open class KotlinFileExtractor(
}
}
- // If this is [someType]ArrayOf(*x), x, otherwise null
- val clonedArray = arg?.let {
- if (arg.elements.size == 1) {
- val onlyElement = arg.elements[0]
- if (onlyElement is IrSpreadElement)
- onlyElement.expression
- else null
- } else null
- }
-
- if (clonedArray != null) {
- // This is an array clone: extract is as a call to java.lang.Object.clone
- objectCloneMethod?.let {
- extractRawMethodAccess(it, c, callable, parent, idx, enclosingStmt, listOf(), clonedArray, null)
- }
- } else {
- // This is array creation: extract it as a call to new ArrayType[] { ... }
- val id = tw.getFreshIdLabel()
- val type = useType(c.type)
- tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
- tw.writeExprsKotlinType(id, type.kotlinResult.id)
- val locId = tw.getLocation(c)
- tw.writeHasLocation(id, locId)
- tw.writeCallableEnclosingExpr(id, callable)
-
- if (isBuiltinCallKotlin(c, "arrayOf")) {
- if (c.typeArgumentsCount == 1) {
- val typeArgument = c.getTypeArgument(0)
- if (typeArgument == null) {
- logger.errorElement("Type argument missing in an arrayOf call", c)
- } else {
- extractTypeAccessRecursive(typeArgument, locId, id, -1, callable, enclosingStmt, TypeContext.GENERIC_ARGUMENT)
- }
- } else {
- logger.errorElement("Expected to find one type argument in arrayOf call", c )
- }
- } else {
- val elementType = c.type.getArrayElementType(pluginContext.irBuiltIns)
- extractTypeAccessRecursive(elementType, locId, id, -1, callable, enclosingStmt)
- }
-
- arg?.let {
- val initId = tw.getFreshIdLabel()
- tw.writeExprs_arrayinit(initId, type.javaResult.id, id, -2)
- tw.writeExprsKotlinType(initId, type.kotlinResult.id)
- tw.writeHasLocation(initId, locId)
- tw.writeCallableEnclosingExpr(initId, callable)
- tw.writeStatementEnclosingExpr(initId, enclosingStmt)
- it.elements.forEachIndexed { i, arg -> extractVarargElement(arg, callable, initId, i, enclosingStmt) }
-
- extractConstantInteger(it.elements.size, locId, id, 0, callable, enclosingStmt)
- }
- }
+ extractArrayCreation(arg, c.type, elementType, isPrimitiveArrayCreation, c, parent, idx, callable, enclosingStmt)
}
isBuiltinCall(c, "", "kotlin.jvm") -> {
// Special case for KClass<*>.java, which is used in the Parcelize plugin. In normal cases, this is already rewritten to the property referenced below:
@@ -2687,7 +2696,7 @@ open class KotlinFileExtractor(
val argType = (ext.type as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull
val typeArguments = if (argType == null) listOf() else listOf(argType)
- extractRawMethodAccess(getter, c, callable, parent, idx, enclosingStmt, listOf(), null, ext, typeArguments)
+ extractRawMethodAccess(getter, c, c.type, callable, parent, idx, enclosingStmt, listOf(), null, ext, typeArguments)
}
}
isFunction(target, "kotlin", "(some array type)", { isArrayType(it) }, "iterator") -> {
@@ -2718,7 +2727,7 @@ open class KotlinFileExtractor(
else -> pluginContext.irBuiltIns.anyNType
}
}
- extractRawMethodAccess(iteratorFn, c, callable, parent, idx, enclosingStmt, listOf(c.dispatchReceiver), null, null, typeArgs)
+ extractRawMethodAccess(iteratorFn, c, c.type, callable, parent, idx, enclosingStmt, listOf(c.dispatchReceiver), null, null, typeArgs)
}
}
}
@@ -2807,6 +2816,7 @@ open class KotlinFileExtractor(
extractRawMethodAccess(
realCallee,
c,
+ c.type,
callable,
parent,
idx,
@@ -2834,6 +2844,7 @@ open class KotlinFileExtractor(
extractRawMethodAccess(
realCallee,
c,
+ c.type,
callable,
parent,
idx,
@@ -2851,6 +2862,51 @@ open class KotlinFileExtractor(
}
}
+ private fun extractArrayCreation(elementList: IrVararg?, resultType: IrType, elementType: IrType?, allowPrimitiveElementType: Boolean, locElement: IrElement, parent: Label, idx: Int, enclosingCallable: Label, enclosingStmt: Label) {
+ // If this is [someType]ArrayOf(*x), x, otherwise null
+ val clonedArray = elementList?.let {
+ if (it.elements.size == 1) {
+ val onlyElement = it.elements[0]
+ if (onlyElement is IrSpreadElement)
+ onlyElement.expression
+ else null
+ } else null
+ }
+
+ if (clonedArray != null) {
+ // This is an array clone: extract is as a call to java.lang.Object.clone
+ objectCloneMethod?.let {
+ extractRawMethodAccess(it, locElement, resultType, enclosingCallable, parent, idx, enclosingStmt, listOf(), clonedArray, null)
+ }
+ } else {
+ // This is array creation: extract it as a call to new ArrayType[] { ... }
+ val id = tw.getFreshIdLabel()
+ val type = useType(resultType)
+ tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
+ tw.writeExprsKotlinType(id, type.kotlinResult.id)
+ val locId = tw.getLocation(locElement)
+ tw.writeHasLocation(id, locId)
+ tw.writeCallableEnclosingExpr(id, enclosingCallable)
+
+ if (elementType != null) {
+ val typeContext = if (allowPrimitiveElementType) TypeContext.OTHER else TypeContext.GENERIC_ARGUMENT
+ extractTypeAccessRecursive(elementType, locId, id, -1, enclosingCallable, enclosingStmt, typeContext)
+ }
+
+ if (elementList != null) {
+ val initId = tw.getFreshIdLabel()
+ tw.writeExprs_arrayinit(initId, type.javaResult.id, id, -2)
+ tw.writeExprsKotlinType(initId, type.kotlinResult.id)
+ tw.writeHasLocation(initId, locId)
+ tw.writeCallableEnclosingExpr(initId, enclosingCallable)
+ tw.writeStatementEnclosingExpr(initId, enclosingStmt)
+ elementList.elements.forEachIndexed { i, arg -> extractVarargElement(arg, enclosingCallable, initId, i, enclosingStmt) }
+
+ extractConstantInteger(elementList.elements.size, locId, id, 0, enclosingCallable, enclosingStmt)
+ }
+ }
+ }
+
private fun extractNewExpr(
methodId: Label,
constructedType: TypeResults,
@@ -2909,20 +2965,8 @@ open class KotlinFileExtractor(
logger.errorElement("Constructor call has non-simple type ${eType.javaClass}", e)
return
}
+ val type = useType(eType)
val isAnonymous = eType.isAnonymous
- val type: TypeResults = if (isAnonymous) {
- if (e.typeArgumentsCount > 0) {
- logger.warnElement("Unexpected type arguments (${e.typeArgumentsCount}) for anonymous class constructor call", e)
- }
- val c = eType.classifier.owner
- if (c !is IrClass) {
- logger.errorElement("Anonymous constructor call type not a class (${c.javaClass})", e)
- return
- }
- useAnonymousClass(c)
- } else {
- useType(eType)
- }
val locId = tw.getLocation(e)
val valueArgs = (0 until e.valueArgumentsCount).map { e.getValueArgument(it) }
// For now, don't try to use default methods for enum constructor calls,
@@ -2939,7 +2983,7 @@ open class KotlinFileExtractor(
}
if (isAnonymous) {
- tw.writeIsAnonymClass(type.javaResult.id.cast(), id)
+ tw.writeIsAnonymClass(type.javaResult.id.cast(), id)
}
val dr = e.dispatchReceiver
@@ -3634,14 +3678,12 @@ open class KotlinFileExtractor(
extractTypeOperatorCall(e, callable, exprParent.parent, exprParent.idx, exprParent.enclosingStmt)
}
is IrVararg -> {
- var spread = e.elements.getOrNull(0) as? IrSpreadElement
- if (spread == null || e.elements.size != 1) {
- logger.errorElement("Unexpected IrVararg", e)
- return
- }
// There are lowered IR cases when the vararg expression is not within a call, such as
- // val temp0 = [*expr]
- extractExpression(spread.expression, callable, parent)
+ // val temp0 = [*expr].
+ // This AST element can also occur as a collection literal in an annotation class, such as
+ // annotation class Ann(val strings: Array = [])
+ val exprParent = parent.expr(e, callable)
+ extractArrayCreation(e, e.type, e.varargElementType, true, e, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt)
}
is IrGetObjectValue -> {
// For `object MyObject { ... }`, the .class has an
@@ -4229,9 +4271,11 @@ open class KotlinFileExtractor(
* this.dispatchReceiver = dispatchReceiver
* }
*
- * fun get(): R { return this.dispatchReceiver.FN1() }
+ * override fun get(): R { return this.dispatchReceiver.FN1() }
*
- * fun set(a0: R): Unit { return this.dispatchReceiver.FN2(a0) }
+ * override fun set(a0: R): Unit { return this.dispatchReceiver.FN2(a0) }
+ *
+ * override fun invoke(): R { return this.get() }
* }
* ```
*
@@ -4269,8 +4313,8 @@ open class KotlinFileExtractor(
)
val declarationParent = peekDeclStackAsDeclarationParent(propertyReferenceExpr) ?: return
- val prefix = if (kPropertyClass.owner.name.asString().startsWith("KMutableProperty")) "Mutable" else ""
- val baseClass = pluginContext.referenceClass(FqName("kotlin.jvm.internal.${prefix}PropertyReference${kPropertyType.arguments.size - 1}"))?.owner?.typeWith()
+ // The base class could be `Any`. `PropertyReference` is used to keep symmetry with function references.
+ val baseClass = pluginContext.referenceClass(FqName("kotlin.jvm.internal.PropertyReference"))?.owner?.typeWith()
?: pluginContext.irBuiltIns.anyType
val classId = extractGeneratedClass(ids, listOf(baseClass, kPropertyType), locId, propertyReferenceExpr, declarationParent)
@@ -4373,10 +4417,16 @@ open class KotlinFileExtractor(
callable: Label
) {
with("function reference", functionReferenceExpr) {
- val target = functionReferenceExpr.reflectionTarget ?: run {
- logger.warnElement("Expected to find reflection target for function reference. Using underlying symbol instead.", functionReferenceExpr)
- functionReferenceExpr.symbol
- }
+ val target =
+ if (functionReferenceExpr.origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE)
+ // For an adaptation (e.g. to adjust the number or type of arguments or results), the symbol field points at the adapter while `.reflectionTarget` points at the source-level target.
+ functionReferenceExpr.symbol
+ else
+ // TODO: Consider whether we could always target the symbol
+ functionReferenceExpr.reflectionTarget ?: run {
+ logger.warnElement("Expected to find reflection target for function reference. Using underlying symbol instead.", functionReferenceExpr)
+ functionReferenceExpr.symbol
+ }
/*
* Extract generated class:
@@ -4480,7 +4530,10 @@ open class KotlinFileExtractor(
val baseClass = pluginContext.referenceClass(FqName("kotlin.jvm.internal.FunctionReference"))?.owner?.typeWith()
?: pluginContext.irBuiltIns.anyType
- val classId = extractGeneratedClass(ids, listOf(baseClass, fnInterfaceType), locId, functionReferenceExpr, declarationParent)
+ val classId = extractGeneratedClass(ids, listOf(baseClass, fnInterfaceType), locId, functionReferenceExpr, declarationParent, { it.valueParameters.size == 1 }) {
+ // The argument to FunctionReference's constructor is the function arity.
+ extractConstantInteger(type.arguments.size - 1, locId, it, 0, ids.constructor, it)
+ }
helper.extractReceiverField()
@@ -4588,7 +4641,7 @@ open class KotlinFileExtractor(
Pair(paramId, paramType)
}
- val paramsSignature = parameters.joinToString(separator = ",", prefix = "(", postfix = ")") { it.second.javaResult.signature }
+ val paramsSignature = parameters.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.second.javaResult, declarationStack.tryPeek()?.first) }
val rt = useType(returnType, TypeContext.RETURN)
tw.writeMethods(methodId, name, "$name$paramsSignature", rt.javaResult.id, parentId, methodId)
@@ -5029,7 +5082,10 @@ open class KotlinFileExtractor(
return
}
- if (!st.isFunctionOrKFunction() && !st.isSuspendFunctionOrKFunction()) {
+ fun IrSimpleType.isKProperty() =
+ classFqName?.asString()?.startsWith("kotlin.reflect.KProperty") == true
+
+ if (!st.isFunctionOrKFunction() && !st.isSuspendFunctionOrKFunction() && !st.isKProperty()) {
logger.errorElement("Expected to find expression with function type in SAM conversion.", e)
return
}
@@ -5217,7 +5273,9 @@ open class KotlinFileExtractor(
superTypes: List,
locId: Label,
elementToReportOn: IrElement,
- declarationParent: IrDeclarationParent
+ declarationParent: IrDeclarationParent,
+ superConstructorSelector: (IrFunction) -> Boolean = { it.valueParameters.isEmpty() },
+ extractSuperconstructorArgs: (Label) -> Unit = {}
): Label {
// Write class
val id = ids.type.javaResult.id.cast()
@@ -5242,7 +5300,7 @@ open class KotlinFileExtractor(
if (baseClass == null) {
logger.warnElement("Cannot find base class", elementToReportOn)
} else {
- val baseConstructor = baseClass.owner.declarations.findSubType { it.symbol is IrConstructorSymbol }
+ val baseConstructor = baseClass.owner.declarations.findSubType { it.symbol is IrConstructorSymbol && superConstructorSelector(it) }
if (baseConstructor == null) {
logger.warnElement("Cannot find base constructor", elementToReportOn)
} else {
@@ -5253,6 +5311,7 @@ open class KotlinFileExtractor(
tw.writeHasLocation(superCallId, locId)
tw.writeCallableBinding(superCallId.cast(), baseConstructorId)
+ extractSuperconstructorArgs(superCallId)
}
}
@@ -5266,7 +5325,7 @@ open class KotlinFileExtractor(
}
/**
- * Extracts the class around a local function or a lambda.
+ * Extracts the class around a local function or a lambda. The superclass must have a no-arg constructor.
*/
private fun extractGeneratedClass(localFunction: IrFunction, superTypes: List) : Label {
with("generated class", localFunction) {
@@ -5301,6 +5360,8 @@ open class KotlinFileExtractor(
fun peek() = stack.peek()
+ fun tryPeek() = if (stack.isEmpty()) null else stack.peek()
+
fun findOverriddenAttributes(f: IrFunction) =
stack.lastOrNull { it.first == f } ?.second
diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt
index 1bd27278da0..f7f553f03d9 100644
--- a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt
+++ b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt
@@ -138,13 +138,13 @@ open class KotlinUsesExtractor(
val newTrapWriter = tw.makeFileTrapWriter(filePath, true)
val newLoggerTrapWriter = logger.tw.makeFileTrapWriter(filePath, false)
val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter)
- return KotlinFileExtractor(newLogger, newTrapWriter, filePath, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
+ return KotlinFileExtractor(newLogger, newTrapWriter, null, filePath, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
}
val newTrapWriter = tw.makeSourceFileTrapWriter(clsFile, true)
val newLoggerTrapWriter = logger.tw.makeSourceFileTrapWriter(clsFile, false)
val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter)
- return KotlinFileExtractor(newLogger, newTrapWriter, clsFile.path, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
+ return KotlinFileExtractor(newLogger, newTrapWriter, null, clsFile.path, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
}
// The Kotlin compiler internal representation of Outer.Inner.InnerInner is InnerInner. This function returns just `R`.
@@ -210,10 +210,6 @@ open class KotlinUsesExtractor(
// `typeArgs` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
fun useClassInstance(c: IrClass, typeArgs: List?, inReceiverContext: Boolean = false): UseClassInstanceResult {
- if (c.isAnonymousObject) {
- logger.error("Unexpected access to anonymous class instance")
- }
-
val substituteClass = getJavaEquivalentClass(c)
val extractClass = substituteClass ?: c
@@ -418,10 +414,11 @@ open class KotlinUsesExtractor(
}
val fqName = replacedClass.fqNameWhenAvailable
- val signature = if (fqName == null) {
+ val signature = if (replacedClass.isAnonymousObject) {
+ null
+ } else if (fqName == null) {
logger.error("Unable to find signature/fqName for ${replacedClass.name}")
- // TODO: Should we return null here instead?
- ""
+ null
} else {
fqName.asString()
}
@@ -465,7 +462,7 @@ open class KotlinUsesExtractor(
}
}
- fun useAnonymousClass(c: IrClass) =
+ private fun useAnonymousClass(c: IrClass) =
tw.lm.anonymousTypeMapping.getOrPut(c) {
TypeResults(
TypeResult(tw.getFreshIdLabel(), "", ""),
@@ -473,14 +470,6 @@ open class KotlinUsesExtractor(
)
}
- fun getExistingAnonymousClassLabel(c: IrClass): Label? {
- if (!c.isAnonymousObject){
- return null
- }
-
- return tw.lm.anonymousTypeMapping[c]?.javaResult?.id
- }
-
fun fakeKotlinType(): Label {
val fakeKotlinPackageId: Label = tw.getLabelFor("@\"FakeKotlinPackage\"", {
tw.writePackages(it, "fake.kotlin")
@@ -497,16 +486,6 @@ open class KotlinUsesExtractor(
// `args` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
fun useSimpleTypeClass(c: IrClass, args: List?, hasQuestionMark: Boolean): TypeResults {
- if (c.isAnonymousObject) {
- args?.let {
- if (it.isNotEmpty() && !isUnspecialised(c, it, logger)) {
- logger.error("Unexpected specialised instance of generic anonymous class")
- }
- }
-
- return useAnonymousClass(c)
- }
-
val classInstanceResult = useClassInstance(c, args)
val javaClassId = classInstanceResult.typeResult.id
val kotlinQualClassName = getUnquotedClassLabel(c, args).classLabel
@@ -795,7 +774,7 @@ open class KotlinUsesExtractor(
extractFileClass(dp)
}
is IrClass ->
- if (classTypeArguments != null && !dp.isAnonymousObject) {
+ if (classTypeArguments != null) {
useClassInstance(dp, classTypeArguments, inReceiverContext).typeResult.id
} else {
val replacedType = tryReplaceParcelizeRawType(dp)
@@ -944,7 +923,7 @@ open class KotlinUsesExtractor(
private val jvmWildcardAnnotation = FqName("kotlin.jvm.JvmWildcard")
- private val jvmWildcardSuppressionAnnotaton = FqName("kotlin.jvm.JvmSuppressWildcards")
+ private val jvmWildcardSuppressionAnnotation = FqName("kotlin.jvm.JvmSuppressWildcards")
private fun arrayExtendsAdditionAllowed(t: IrSimpleType): Boolean =
// Note the array special case includes Array<*>, which does permit adding `? extends ...` (making `? extends Object[]` in that case)
@@ -977,7 +956,7 @@ open class KotlinUsesExtractor(
when {
t.hasAnnotation(jvmWildcardAnnotation) -> true
!addByDefault -> false
- t.hasAnnotation(jvmWildcardSuppressionAnnotaton) -> false
+ t.hasAnnotation(jvmWildcardSuppressionAnnotation) -> false
v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny())
v == Variance.OUT_VARIANCE -> extendsAdditionAllowed(t)
else -> false
@@ -1225,9 +1204,9 @@ open class KotlinUsesExtractor(
}
fun hasWildcardSuppressionAnnotation(d: IrDeclaration) =
- d.hasAnnotation(jvmWildcardSuppressionAnnotaton) ||
+ d.hasAnnotation(jvmWildcardSuppressionAnnotation) ||
// Note not using `parentsWithSelf` as that only works if `d` is an IrDeclarationParent
- d.parents.any { (it as? IrAnnotationContainer)?.hasAnnotation(jvmWildcardSuppressionAnnotaton) == true }
+ d.parents.any { (it as? IrAnnotationContainer)?.hasAnnotation(jvmWildcardSuppressionAnnotation) == true }
/**
* Class to hold labels for generated classes around local functions, lambdas, function references, and property references.
@@ -1319,6 +1298,12 @@ open class KotlinUsesExtractor(
}
} ?: f
+ fun isPrivate(d: IrDeclaration) =
+ when(d) {
+ is IrDeclarationWithVisibility -> d.visibility.let { it == DescriptorVisibilities.PRIVATE || it == DescriptorVisibilities.PRIVATE_TO_THIS }
+ else -> false
+ }
+
fun useFunction(f: IrFunction, classTypeArgsIncludingOuterClasses: List? = null, noReplace: Boolean = false): Label {
return useFunction(f, null, classTypeArgsIncludingOuterClasses, noReplace)
}
@@ -1330,7 +1315,9 @@ open class KotlinUsesExtractor(
}
val javaFun = kotlinFunctionToJavaEquivalent(f, noReplace)
val label = getFunctionLabel(javaFun, parentId, classTypeArgsIncludingOuterClasses)
- val id: Label = tw.getLabelFor(label)
+ val id: Label = tw.getLabelFor(label) {
+ extractPrivateSpecialisedDeclaration(f, classTypeArgsIncludingOuterClasses)
+ }
if (isExternalDeclaration(javaFun)) {
extractFunctionLaterIfExternalFileMember(javaFun)
extractExternalEnclosingClassLater(javaFun)
@@ -1338,6 +1325,19 @@ open class KotlinUsesExtractor(
return id
}
+ private fun extractPrivateSpecialisedDeclaration(d: IrDeclaration, classTypeArgsIncludingOuterClasses: List?) {
+ // Note here `classTypeArgsIncludingOuterClasses` being null doesn't signify a raw receiver type but rather that no type args were supplied.
+ // This is because a call to a private method can only be observed inside Kotlin code, and Kotlin can't represent raw types.
+ if (this is KotlinFileExtractor && isPrivate(d) && classTypeArgsIncludingOuterClasses != null && classTypeArgsIncludingOuterClasses.isNotEmpty()) {
+ d.parent.let {
+ when(it) {
+ is IrClass -> this.extractDeclarationPrototype(d, useClassInstance(it, classTypeArgsIncludingOuterClasses).typeResult.id, classTypeArgsIncludingOuterClasses)
+ else -> logger.warnElement("Unable to extract specialised declaration that isn't a member of a class", d)
+ }
+ }
+ }
+ }
+
fun getTypeArgumentLabel(
arg: IrTypeArgument
): TypeResultWithoutSignature {
@@ -1393,20 +1393,24 @@ open class KotlinUsesExtractor(
private fun getUnquotedClassLabel(c: IrClass, argsIncludingOuterClasses: List?): ClassLabelResults {
val pkg = c.packageFqName?.asString() ?: ""
val cls = c.name.asString()
- val label = when (val parent = c.parent) {
- is IrClass -> {
- "${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls"
- }
- is IrFunction -> {
- "{${useFunction(parent)}}.$cls"
- }
- is IrField -> {
- "{${useField(parent)}}.$cls"
- }
- else -> {
- if (pkg.isEmpty()) cls else "$pkg.$cls"
- }
- }
+ val label =
+ if (c.isAnonymousObject)
+ "{${useAnonymousClass(c).javaResult.id}}"
+ else
+ when (val parent = c.parent) {
+ is IrClass -> {
+ "${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls"
+ }
+ is IrFunction -> {
+ "{${useFunction(parent)}}.$cls"
+ }
+ is IrField -> {
+ "{${useField(parent)}}.$cls"
+ }
+ else -> {
+ if (pkg.isEmpty()) cls else "$pkg.$cls"
+ }
+ }
val reorderedArgs = orderTypeArgsLeftToRight(c, argsIncludingOuterClasses)
val typeArgLabels = reorderedArgs?.map { getTypeArgumentLabel(it) }
@@ -1417,20 +1421,17 @@ open class KotlinUsesExtractor(
""
else
typeArgLabels.takeLast(c.typeParameters.size).joinToString(prefix = "<", postfix = ">", separator = ",") { it.shortName }
+ val shortNamePrefix = if (c.isAnonymousObject) "" else cls
return ClassLabelResults(
label + (typeArgLabels?.joinToString(separator = "") { ";{${it.id}}" } ?: "<>"),
- cls + typeArgsShortName
+ shortNamePrefix + typeArgsShortName
)
}
// `args` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
fun getClassLabel(c: IrClass, argsIncludingOuterClasses: List?): ClassLabelResults {
- if (c.isAnonymousObject) {
- logger.error("Label generation should not be requested for an anonymous class")
- }
-
val unquotedLabel = getUnquotedClassLabel(c, argsIncludingOuterClasses)
return ClassLabelResults(
"@\"class;${unquotedLabel.classLabel}\"",
@@ -1438,10 +1439,6 @@ open class KotlinUsesExtractor(
}
fun useClassSource(c: IrClass): Label {
- if (c.isAnonymousObject) {
- return useAnonymousClass(c).javaResult.id.cast()
- }
-
// For source classes, the label doesn't include any type arguments
val classTypeResult = addClassLabel(c, listOf())
return classTypeResult.id
@@ -1686,8 +1683,11 @@ open class KotlinUsesExtractor(
}
}
- fun useProperty(p: IrProperty, parentId: Label, classTypeArgsIncludingOuterClasses: List?): Label =
- tw.getLabelFor(getPropertyLabel(p, parentId, classTypeArgsIncludingOuterClasses)).also { extractPropertyLaterIfExternalFileMember(p) }
+ fun useProperty(p: IrProperty, parentId: Label, classTypeArgsIncludingOuterClasses: List?) =
+ tw.getLabelFor(getPropertyLabel(p, parentId, classTypeArgsIncludingOuterClasses)) {
+ extractPropertyLaterIfExternalFileMember(p)
+ extractPrivateSpecialisedDeclaration(p, classTypeArgsIncludingOuterClasses)
+ }
fun getEnumEntryLabel(ee: IrEnumEntry): String {
val parentId = useDeclarationParent(ee.parent, false)
diff --git a/java/kotlin-extractor/src/main/kotlin/LinesOfCode.kt b/java/kotlin-extractor/src/main/kotlin/LinesOfCode.kt
new file mode 100644
index 00000000000..8fb8869443e
--- /dev/null
+++ b/java/kotlin-extractor/src/main/kotlin/LinesOfCode.kt
@@ -0,0 +1,127 @@
+package com.github.codeql
+
+import com.github.codeql.utils.versions.Psi2Ir
+import com.intellij.psi.PsiComment
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiWhiteSpace
+import org.jetbrains.kotlin.ir.IrElement
+import org.jetbrains.kotlin.ir.declarations.*
+import org.jetbrains.kotlin.kdoc.psi.api.KDocElement
+import org.jetbrains.kotlin.psi.KtCodeFragment
+import org.jetbrains.kotlin.psi.KtVisitor
+
+class LinesOfCode(
+ val logger: FileLogger,
+ val tw: FileTrapWriter,
+ val file: IrFile
+) {
+ val psi2Ir = Psi2Ir(logger)
+
+ fun linesOfCodeInFile(id: Label) {
+ val ktFile = psi2Ir.getKtFile(file)
+ if (ktFile == null) {
+ return
+ }
+ linesOfCodeInPsi(id, ktFile, file)
+ }
+
+ fun linesOfCodeInDeclaration(d: IrDeclaration, id: Label) {
+ val p = psi2Ir.findPsiElement(d, file)
+ if (p == null) {
+ return
+ }
+ linesOfCodeInPsi(id, p, d)
+ }
+
+ private fun linesOfCodeInPsi(id: Label, root: PsiElement, e: IrElement) {
+ val document = root.getContainingFile().getViewProvider().getDocument()
+ if (document == null) {
+ logger.errorElement("Cannot find document for PSI", e)
+ tw.writeNumlines(id, 0, 0, 0)
+ return
+ }
+
+ val rootRange = root.getTextRange()
+ val rootFirstLine = document.getLineNumber(rootRange.getStartOffset())
+ val rootLastLine = document.getLineNumber(rootRange.getEndOffset())
+ if (rootLastLine < rootFirstLine) {
+ logger.errorElement("PSI ends before it starts", e)
+ tw.writeNumlines(id, 0, 0, 0)
+ return
+ }
+ val numLines = 1 + rootLastLine - rootFirstLine
+ val lineContents = Array(numLines) { LineContent() }
+
+ val visitor =
+ object : KtVisitor() {
+ override fun visitElement(element: PsiElement) {
+ val isComment = element is PsiComment
+ // Comments may include nodes that aren't PsiComments,
+ // so we don't want to visit them or we'll think they
+ // are code.
+ if (!isComment) {
+ element.acceptChildren(this)
+ }
+
+ if (element is PsiWhiteSpace) {
+ return
+ }
+ // Leaf nodes are assumed to be tokens, and
+ // therefore we count any lines that they are on.
+ // For comments, we actually need to look at the
+ // outermost node, as the leaves of KDocs don't
+ // necessarily cover all lines.
+ if (isComment || element.getChildren().size == 0) {
+ val range = element.getTextRange()
+ val startOffset = range.getStartOffset()
+ val endOffset = range.getEndOffset()
+ // The PSI doesn't seem to have anything like
+ // the IR's UNDEFINED_OFFSET and SYNTHETIC_OFFSET,
+ // but < 0 still seem to represent bad/unknown
+ // locations.
+ if (startOffset < 0 || endOffset < 0) {
+ logger.errorElement("PSI has negative offset", e)
+ return
+ }
+ if (startOffset > endOffset) {
+ return
+ }
+ // We might get e.g. an import list for a file
+ // with no imports, which claims to have start
+ // and end offsets of 0. Anything of 0 width
+ // we therefore just skip.
+ if (startOffset == endOffset) {
+ return
+ }
+ val firstLine = document.getLineNumber(startOffset)
+ val lastLine = document.getLineNumber(endOffset)
+ if (firstLine < rootFirstLine) {
+ logger.errorElement("PSI element starts before root", e)
+ return
+ } else if (lastLine > rootLastLine) {
+ logger.errorElement("PSI element ends after root", e)
+ return
+ }
+ for (line in firstLine..lastLine) {
+ val lineContent = lineContents[line - rootFirstLine]
+ if (isComment) {
+ lineContent.containsComment = true
+ } else {
+ lineContent.containsCode = true
+ }
+ }
+ }
+ }
+ }
+ root.accept(visitor)
+ val total = lineContents.size
+ val code = lineContents.count { it.containsCode }
+ val comment = lineContents.count { it.containsComment }
+ tw.writeNumlines(id, total, code, comment)
+ }
+
+ private class LineContent {
+ var containsComment = false
+ var containsCode = false
+ }
+}
diff --git a/java/kotlin-extractor/src/main/kotlin/comments/CommentExtractor.kt b/java/kotlin-extractor/src/main/kotlin/comments/CommentExtractor.kt
index eb09685905c..cae3174ecbd 100644
--- a/java/kotlin-extractor/src/main/kotlin/comments/CommentExtractor.kt
+++ b/java/kotlin-extractor/src/main/kotlin/comments/CommentExtractor.kt
@@ -127,12 +127,7 @@ class CommentExtractor(private val fileExtractor: KotlinFileExtractor, private v
// local functions are not named globally, so we need to get them from the local function label cache
label = "local function ${element.name.asString()}"
fileExtractor.getExistingLocallyVisibleFunctionLabel(element)
- } else if (element is IrClass && element.isAnonymousObject) {
- // anonymous objects are not named globally, so we need to get them from the cache
- label = "anonymous class ${element.name.asString()}"
- fileExtractor.getExistingAnonymousClassLabel(element)
- }
- else {
+ } else {
label = getLabelForNamedElement(element) ?: return null
tw.getExistingLabelFor(label)
}
@@ -145,12 +140,7 @@ class CommentExtractor(private val fileExtractor: KotlinFileExtractor, private v
private fun getLabelForNamedElement(element: IrElement) : String? {
when (element) {
- is IrClass ->
- return if (element.isAnonymousObject) {
- null
- } else {
- fileExtractor.getClassLabel(element, listOf()).classLabel
- }
+ is IrClass -> return fileExtractor.getClassLabel(element, listOf()).classLabel
is IrTypeParameter -> return fileExtractor.getTypeParameterLabel(element)
is IrFunction -> {
return if (element.isLocalFunction()) {
diff --git a/java/kotlin-extractor/src/main/kotlin/utils/TypeResults.kt b/java/kotlin-extractor/src/main/kotlin/utils/TypeResults.kt
index 428c7dab718..85bbf728b47 100644
--- a/java/kotlin-extractor/src/main/kotlin/utils/TypeResults.kt
+++ b/java/kotlin-extractor/src/main/kotlin/utils/TypeResults.kt
@@ -12,7 +12,7 @@ package com.github.codeql
* `shortName` is a Java primitive name (e.g. "int"), a class short name with Java-style type arguments ("InnerClass" or
* "OuterClass" or "OtherClass extends Bound>") or an array ("componentShortName[]").
*/
-data class TypeResultGeneric(val id: Label, val signature: SignatureType, val shortName: String) {
+data class TypeResultGeneric(val id: Label, val signature: SignatureType?, val shortName: String) {
fun cast(): TypeResultGeneric {
@Suppress("UNCHECKED_CAST")
return this as TypeResultGeneric
diff --git a/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.expected b/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.expected
index 183abf9a986..fa16a8a7d81 100644
--- a/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.expected
+++ b/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.expected
@@ -1,2 +1,3 @@
| CodeQL Kotlin extractor | 2 | | IrProperty without a getter | d.kt:0:0:0:0 | d.kt:0:0:0:0 |
-| CodeQL Kotlin extractor | 2 | | Not rewriting trap file for: Boolean -1.0-0- -1.0-0-null test-db/trap/java/classes/kotlin/Boolean.members.trap.gz | file://:0:0:0:0 | file://:0:0:0:0 |
+| CodeQL Kotlin extractor | 2 | | Not rewriting trap file for test-db/trap/java/classes/java/lang/Boolean.members/Boolean.members--kotlin.trap.gz as it exists | file://:0:0:0:0 | file://:0:0:0:0 |
+| CodeQL Kotlin extractor | 2 | | Not rewriting trap file for test-db/trap/java/classes/kotlin/Boolean.members/Boolean.members--null.trap.gz as it exists | file://:0:0:0:0 | file://:0:0:0:0 |
diff --git a/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.ql b/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.ql
index 57ec32bb048..94e2c43d437 100644
--- a/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.ql
+++ b/java/ql/integration-tests/linux-only/kotlin/custom_plugin/diagnostics.ql
@@ -1,13 +1,14 @@
import java
-from string genBy, int severity, string tag, string msg, Location l
+from string genBy, int severity, string tag, string msg, string msg2, Location l
where
diagnostics(_, genBy, severity, tag, msg, _, l) and
(
// Different installations get different sets of these messages,
// so we filter out all but one that happens everywhere.
- msg.matches("Not rewriting trap file for: %")
+ msg.matches("Not rewriting trap file for %")
implies
- msg.matches("Not rewriting trap file for: Boolean %")
- )
-select genBy, severity, tag, msg, l
+ msg.matches("Not rewriting trap file for %Boolean.members%")
+ ) and
+ msg2 = msg.regexpReplaceAll("#-?[0-9]+\\.-?[0-9]+--?[0-9]+-", "--")
+select genBy, severity, tag, msg2, l
diff --git a/java/ql/integration-tests/posix-only/kotlin/java_modifiers/libsrc/extlib/A.java b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/libsrc/extlib/A.java
new file mode 100644
index 00000000000..aa577f6526b
--- /dev/null
+++ b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/libsrc/extlib/A.java
@@ -0,0 +1,6 @@
+package extlib;
+
+public class A {
+ protected void m() {}
+}
+
diff --git a/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.expected b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.expected
new file mode 100644
index 00000000000..459a8d9209d
--- /dev/null
+++ b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.expected
@@ -0,0 +1,2 @@
+| extlib.jar/extlib/A.class:0:0:0:0 | m | protected |
+| test.kt:4:12:4:22 | m | override, protected |
diff --git a/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.kt b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.kt
new file mode 100644
index 00000000000..49f233036b4
--- /dev/null
+++ b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.kt
@@ -0,0 +1,6 @@
+import extlib.A;
+
+class B : A() {
+ override fun m() { }
+}
+
diff --git a/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.py b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.py
new file mode 100644
index 00000000000..31c641d7013
--- /dev/null
+++ b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.py
@@ -0,0 +1,10 @@
+from create_database_utils import *
+import glob
+
+# Compile Java untraced. Note the Java source is hidden under `javasrc` so the Kotlin compiler
+# will certainly reference the jar, not the source or class file for extlib.Lib
+
+os.mkdir('build')
+runSuccessfully(["javac"] + glob.glob("libsrc/extlib/*.java") + ["-d", "build"])
+runSuccessfully(["jar", "cf", "extlib.jar", "-C", "build", "extlib"])
+run_codeql_database_create(["kotlinc test.kt -cp extlib.jar"], lang="java")
diff --git a/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.ql b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.ql
new file mode 100644
index 00000000000..3c4cf8ac898
--- /dev/null
+++ b/java/ql/integration-tests/posix-only/kotlin/java_modifiers/test.ql
@@ -0,0 +1,6 @@
+import java
+
+query predicate mods(Method m, string modifiers) {
+ m.getName() = "m" and
+ modifiers = concat(string s | m.hasModifier(s) | s, ", ")
+}
diff --git a/java/ql/lib/change-notes/2022-09-29-contentprovider-incomplete-permissions.md b/java/ql/lib/change-notes/2022-09-29-contentprovider-incomplete-permissions.md
new file mode 100644
index 00000000000..db4da90e5e9
--- /dev/null
+++ b/java/ql/lib/change-notes/2022-09-29-contentprovider-incomplete-permissions.md
@@ -0,0 +1,4 @@
+---
+category: feature
+---
+* Added a new predicate, `hasIncompletePermissions`, in the `AndroidProviderXmlElement` class. This predicate detects if a provider element does not provide both read and write permissions.
diff --git a/java/ql/lib/change-notes/2022-10-11-modifiable-type-variable.md b/java/ql/lib/change-notes/2022-10-11-modifiable-type-variable.md
new file mode 100644
index 00000000000..38ce11b96b1
--- /dev/null
+++ b/java/ql/lib/change-notes/2022-10-11-modifiable-type-variable.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The class `TypeVariable` now also extends `Modifiable`.
diff --git a/java/ql/lib/change-notes/2022-10-13-stream-collect.md b/java/ql/lib/change-notes/2022-10-13-stream-collect.md
new file mode 100644
index 00000000000..bd7f6c3e8d4
--- /dev/null
+++ b/java/ql/lib/change-notes/2022-10-13-stream-collect.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* Added support for common patterns involving `Stream.collect` and common collectors like `Collectors.toList()`.
diff --git a/java/ql/lib/config/semmlecode.dbscheme b/java/ql/lib/config/semmlecode.dbscheme
index ecb42310286..709f1d1fd04 100644
--- a/java/ql/lib/config/semmlecode.dbscheme
+++ b/java/ql/lib/config/semmlecode.dbscheme
@@ -1033,7 +1033,7 @@ javadocText(
@boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
@kt_property;
-@modifiable = @member_modifiable| @param | @localvar ;
+@modifiable = @member_modifiable| @param | @localvar | @typevariable;
@member_modifiable = @class | @interface | @method | @constructor | @field | @kt_property;
diff --git a/java/ql/lib/semmle/code/java/Generics.qll b/java/ql/lib/semmle/code/java/Generics.qll
index 95471437988..54cab14fe40 100644
--- a/java/ql/lib/semmle/code/java/Generics.qll
+++ b/java/ql/lib/semmle/code/java/Generics.qll
@@ -137,7 +137,7 @@ abstract class BoundedType extends RefType, @boundedtype {
* For example, `T` is a type parameter in
* `class X { }` and in ` void m() { }`.
*/
-class TypeVariable extends BoundedType, @typevariable {
+class TypeVariable extends BoundedType, Modifiable, @typevariable {
/** Gets the generic type that is parameterized by this type parameter, if any. */
GenericType getGenericType() { typeVars(this, _, _, _, result) }
diff --git a/java/ql/lib/semmle/code/java/Member.qll b/java/ql/lib/semmle/code/java/Member.qll
index 456e6e8b12a..9aa1f8d31c0 100644
--- a/java/ql/lib/semmle/code/java/Member.qll
+++ b/java/ql/lib/semmle/code/java/Member.qll
@@ -294,6 +294,48 @@ class Callable extends StmtParent, Member, @callable {
constrs(this, _, result, _, _, _) or
methods(this, _, result, _, _, _)
}
+
+ /**
+ * Gets this callable's Kotlin proxy that supplies default parameter values, if one exists.
+ *
+ * For example, for the Kotlin declaration `fun f(x: Int, y: Int = 0, z: String = "1")`,
+ * this will get the synthetic proxy method that fills in the default values for `y` and `z`
+ * if not supplied, and to which the Kotlin extractor dispatches calls to `f` that are missing
+ * one or more parameter value. Similarly, constructors with one or more default parameter values
+ * have a corresponding constructor that fills in default values.
+ */
+ Callable getKotlinParameterDefaultsProxy() {
+ this.getDeclaringType() = result.getDeclaringType() and
+ exists(int proxyNParams, int extraLeadingParams, RefType lastParamType |
+ proxyNParams = result.getNumberOfParameters() and
+ extraLeadingParams = (proxyNParams - this.getNumberOfParameters()) - 2 and
+ extraLeadingParams >= 0 and
+ result.getParameterType(proxyNParams - 1) = lastParamType and
+ result.getParameterType(proxyNParams - 2).(PrimitiveType).hasName("int") and
+ (
+ this instanceof Constructor and
+ result instanceof Constructor and
+ extraLeadingParams = 0 and
+ lastParamType.hasQualifiedName("kotlin.jvm.internal", "DefaultConstructorMarker")
+ or
+ this instanceof Method and
+ result instanceof Method and
+ this.getName() + "$default" = result.getName() and
+ extraLeadingParams <= 2 and
+ lastParamType instanceof TypeObject
+ )
+ |
+ forall(int paramIdx | paramIdx in [extraLeadingParams .. proxyNParams - 3] |
+ this.getParameterType(paramIdx - extraLeadingParams).getErasure() =
+ eraseRaw(result.getParameterType(paramIdx))
+ )
+ )
+ }
+}
+
+/** Gets the erasure of `t1` if it is a raw type, or `t1` itself otherwise. */
+private Type eraseRaw(Type t1) {
+ if t1 instanceof RawType then result = t1.getErasure() else result = t1
}
/** Holds if method `m1` overrides method `m2`. */
diff --git a/java/ql/lib/semmle/code/java/Modifier.qll b/java/ql/lib/semmle/code/java/Modifier.qll
index 64c0cd6ec9d..150b65be671 100644
--- a/java/ql/lib/semmle/code/java/Modifier.qll
+++ b/java/ql/lib/semmle/code/java/Modifier.qll
@@ -67,10 +67,10 @@ abstract class Modifiable extends Element {
/** Holds if this element has an `inline` modifier. */
predicate isInline() { this.hasModifier("inline") }
- /** Holds if this element has an `noinline` modifier. */
+ /** Holds if this element has a `noinline` modifier. */
predicate isNoinline() { this.hasModifier("noinline") }
- /** Holds if this element has an `crossinline` modifier. */
+ /** Holds if this element has a `crossinline` modifier. */
predicate isCrossinline() { this.hasModifier("crossinline") }
/** Holds if this element has a `suspend` modifier. */
@@ -93,4 +93,16 @@ abstract class Modifiable extends Element {
/** Holds if this element has a `strictfp` modifier. */
predicate isStrictfp() { this.hasModifier("strictfp") }
+
+ /** Holds if this element has a `lateinit` modifier. */
+ predicate isLateinit() { this.hasModifier("lateinit") }
+
+ /** Holds if this element has a `reified` modifier. */
+ predicate isReified() { this.hasModifier("reified") }
+
+ /** Holds if this element has an `in` modifier. */
+ predicate isIn() { this.hasModifier("in") }
+
+ /** Holds if this element has an `out` modifier. */
+ predicate isOut() { this.hasModifier("out") }
}
diff --git a/java/ql/lib/semmle/code/java/Type.qll b/java/ql/lib/semmle/code/java/Type.qll
index b3fb3ce8e88..9a5852d641a 100644
--- a/java/ql/lib/semmle/code/java/Type.qll
+++ b/java/ql/lib/semmle/code/java/Type.qll
@@ -686,7 +686,7 @@ class SrcRefType extends RefType {
/** A class declaration. */
class Class extends ClassOrInterface, @class {
/** Holds if this class is an anonymous class. */
- predicate isAnonymous() { isAnonymClass(this, _) }
+ predicate isAnonymous() { isAnonymClass(this.getSourceDeclaration(), _) }
override RefType getSourceDeclaration() { classes(this, _, _, result) }
@@ -800,10 +800,13 @@ class AnonymousClass extends NestedClass {
}
/** Gets the class instance expression where this anonymous class occurs. */
- ClassInstanceExpr getClassInstanceExpr() { isAnonymClass(this, result) }
+ ClassInstanceExpr getClassInstanceExpr() { isAnonymClass(this.getSourceDeclaration(), result) }
override string toString() {
- result = "new " + this.getClassInstanceExpr().getTypeName() + "(...) { ... }"
+ // Include super.toString, i.e. the name given in the database, because for Kotlin anonymous
+ // classes we can get specialisations of anonymous generic types, and this will supply the
+ // trailing type arguments.
+ result = "new " + this.getClassInstanceExpr().getTypeName() + "(...) { ... }" + super.toString()
}
/**
diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
index a235f43362d..8460c662d5c 100644
--- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
@@ -75,7 +75,7 @@ import java
private import semmle.code.java.dataflow.DataFlow::DataFlow
private import internal.DataFlowPrivate
private import internal.FlowSummaryImpl::Private::External
-private import internal.FlowSummaryImplSpecific
+private import internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import internal.AccessPathSyntax
private import FlowSummary
@@ -834,7 +834,7 @@ private module Cached {
*/
cached
predicate sourceNode(Node node, string kind) {
- exists(InterpretNode n | isSourceNode(n, kind) and n.asNode() = node)
+ exists(FlowSummaryImplSpecific::InterpretNode n | isSourceNode(n, kind) and n.asNode() = node)
}
/**
@@ -843,7 +843,7 @@ private module Cached {
*/
cached
predicate sinkNode(Node node, string kind) {
- exists(InterpretNode n | isSinkNode(n, kind) and n.asNode() = node)
+ exists(FlowSummaryImplSpecific::InterpretNode n | isSinkNode(n, kind) and n.asNode() = node)
}
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll
index 1792c2e9f11..ed9b0de165d 100644
--- a/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/FlowSummary.qll
@@ -4,7 +4,6 @@
import java
private import internal.FlowSummaryImpl as Impl
-private import internal.DataFlowDispatch
private import internal.DataFlowUtil
// import all instances of SummarizedCallable below
@@ -24,6 +23,12 @@ module SummaryComponent {
/** Gets a summary component for field `f`. */
SummaryComponent field(Field f) { result = content(any(FieldContent c | c.getField() = f)) }
+ /** Gets a summary component for `Element`. */
+ SummaryComponent element() { result = content(any(CollectionContent c)) }
+
+ /** Gets a summary component for `MapValue`. */
+ SummaryComponent mapValue() { result = content(any(MapValueContent c)) }
+
/** Gets a summary component that represents the return value of a call. */
SummaryComponent return() { result = return(_) }
}
@@ -42,10 +47,129 @@ module SummaryComponentStack {
result = push(SummaryComponent::field(f), object)
}
+ /** Gets a stack representing `Element` of `object`. */
+ SummaryComponentStack elementOf(SummaryComponentStack object) {
+ result = push(SummaryComponent::element(), object)
+ }
+
+ /** Gets a stack representing `MapValue` of `object`. */
+ SummaryComponentStack mapValueOf(SummaryComponentStack object) {
+ result = push(SummaryComponent::mapValue(), object)
+ }
+
/** Gets a singleton stack representing a (normal) return. */
SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
}
+/** A synthetic callable with a set of concrete call sites and a flow summary. */
+abstract class SyntheticCallable extends string {
+ bindingset[this]
+ SyntheticCallable() { any() }
+
+ /** Gets a call that targets this callable. */
+ abstract Call getACall();
+
+ /**
+ * Holds if data may flow from `input` to `output` through this callable.
+ *
+ * See `SummarizedCallable::propagatesFlow` for details.
+ */
+ predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ none()
+ }
+
+ /**
+ * Gets the type of the parameter at the specified position with -1 indicating
+ * the instance parameter. If no types are provided then the types default to
+ * `Object`.
+ */
+ Type getParameterType(int pos) { none() }
+
+ /**
+ * Gets the return type of this callable. If no type is provided then the type
+ * defaults to `Object`.
+ */
+ Type getReturnType() { none() }
+}
+
+private newtype TSummarizedCallableBase =
+ TSimpleCallable(Callable c) { c.isSourceDeclaration() } or
+ TSyntheticCallable(SyntheticCallable c)
+
+/**
+ * A callable that may have a flow summary. This is either a regular `Callable`
+ * or a `SyntheticCallable`.
+ */
+class SummarizedCallableBase extends TSummarizedCallableBase {
+ /** Gets a textual representation of this callable. */
+ string toString() { result = this.asCallable().toString() or result = this.asSyntheticCallable() }
+
+ /** Gets the source location for this callable. */
+ Location getLocation() {
+ result = this.asCallable().getLocation()
+ or
+ result.hasLocationInfo("", 0, 0, 0, 0) and
+ this instanceof TSyntheticCallable
+ }
+
+ /** Gets this callable cast as a `Callable`. */
+ Callable asCallable() { this = TSimpleCallable(result) }
+
+ /** Gets this callable cast as a `SyntheticCallable`. */
+ SyntheticCallable asSyntheticCallable() { this = TSyntheticCallable(result) }
+
+ /** Gets a call that targets this callable. */
+ Call getACall() {
+ result.getCallee().getSourceDeclaration() = this.asCallable()
+ or
+ result = this.asSyntheticCallable().getACall()
+ }
+
+ /**
+ * Gets the type of the parameter at the specified position with -1 indicating
+ * the instance parameter.
+ */
+ Type getParameterType(int pos) {
+ result = this.asCallable().getParameterType(pos)
+ or
+ pos = -1 and result = this.asCallable().getDeclaringType()
+ or
+ result = this.asSyntheticCallable().getParameterType(pos)
+ or
+ exists(SyntheticCallable sc | sc = this.asSyntheticCallable() |
+ Impl::Private::summaryParameterNodeRange(this, pos) and
+ not exists(sc.getParameterType(pos)) and
+ result instanceof TypeObject
+ )
+ }
+
+ /** Gets the return type of this callable. */
+ Type getReturnType() {
+ result = this.asCallable().getReturnType()
+ or
+ exists(SyntheticCallable sc | sc = this.asSyntheticCallable() |
+ result = sc.getReturnType()
+ or
+ not exists(sc.getReturnType()) and
+ result instanceof TypeObject
+ )
+ }
+}
+
class SummarizedCallable = Impl::Public::SummarizedCallable;
+/**
+ * An adapter class to add the flow summaries specified on `SyntheticCallable`
+ * to `SummarizedCallable`.
+ */
+private class SummarizedSyntheticCallableAdapter extends SummarizedCallable, TSyntheticCallable {
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ this.asSyntheticCallable().propagatesFlow(input, output, preservesValue)
+ }
+}
+
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
index a57d1ca32be..22e79a2240d 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
@@ -9,9 +9,7 @@ private import semmle.code.java.dispatch.internal.Unification
private module DispatchImpl {
private predicate hasHighConfidenceTarget(Call c) {
- exists(SummarizedCallable sc |
- sc = c.getCallee().getSourceDeclaration() and not sc.isAutoGenerated()
- )
+ exists(SummarizedCallable sc | sc.getACall() = c and not sc.isAutoGenerated())
or
exists(Callable srcTgt |
srcTgt = VirtualDispatch::viableCallable(c) and
@@ -30,7 +28,7 @@ private module DispatchImpl {
DataFlowCallable viableCallable(DataFlowCall c) {
result.asCallable() = sourceDispatch(c.asCall())
or
- result.asSummarizedCallable() = c.asCall().getCallee().getSourceDeclaration()
+ result.asSummarizedCallable().getACall() = c.asCall()
}
/**
@@ -144,7 +142,7 @@ private module DispatchImpl {
not Unification::failsUnification(t, t2)
)
or
- result.asSummarizedCallable() = def
+ result.asSummarizedCallable().getACall() = ma
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForOnActivityResult.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
index 9053019a6d0..b5631b26b0b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
@@ -838,13 +838,13 @@ private module Stage1 implements StageSig {
* by `revFlow`.
*/
pragma[nomagic]
- predicate revFlowIsReadAndStored(Content c, Configuration conf) {
+ additional predicate revFlowIsReadAndStored(Content c, Configuration conf) {
revFlowConsCand(c, conf) and
revFlowStore(c, _, _, conf)
}
pragma[nomagic]
- predicate viableReturnPosOutNodeCandFwd1(
+ additional predicate viableReturnPosOutNodeCandFwd1(
DataFlowCall call, ReturnPosition pos, NodeEx out, Configuration config
) {
fwdFlowReturnPosition(pos, _, config) and
@@ -860,7 +860,7 @@ private module Stage1 implements StageSig {
}
pragma[nomagic]
- predicate viableParamArgNodeCandFwd1(
+ additional predicate viableParamArgNodeCandFwd1(
DataFlowCall call, ParamNodeEx p, ArgNodeEx arg, Configuration config
) {
viableParamArgEx(call, p, arg) and
@@ -907,7 +907,7 @@ private module Stage1 implements StageSig {
)
}
- predicate revFlowState(FlowState state, Configuration config) {
+ additional predicate revFlowState(FlowState state, Configuration config) {
exists(NodeEx node |
sinkNode(node, state, config) and
revFlow(node, _, pragma[only_bind_into](config)) and
@@ -999,7 +999,7 @@ private module Stage1 implements StageSig {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
@@ -1260,7 +1260,7 @@ private module MkStage {
* argument.
*/
pragma[nomagic]
- predicate fwdFlow(
+ additional predicate fwdFlow(
NodeEx node, FlowState state, Cc cc, ApOption argAp, Ap ap, Configuration config
) {
fwdFlow0(node, state, cc, argAp, ap, config) and
@@ -1484,7 +1484,7 @@ private module MkStage {
* the access path of the returned value.
*/
pragma[nomagic]
- predicate revFlow(
+ additional predicate revFlow(
NodeEx node, FlowState state, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
) {
revFlow0(node, state, toReturn, returnAp, ap, config) and
@@ -1662,7 +1662,7 @@ private module MkStage {
)
}
- predicate revFlow(NodeEx node, FlowState state, Configuration config) {
+ additional predicate revFlow(NodeEx node, FlowState state, Configuration config) {
revFlow(node, state, _, _, _, config)
}
@@ -1675,11 +1675,13 @@ private module MkStage {
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, Configuration config) { revFlow(node, _, _, _, _, config) }
+ additional predicate revFlowAlias(NodeEx node, Configuration config) {
+ revFlow(node, _, _, _, _, config)
+ }
// use an alias as a workaround for bad functionality-induced joins
pragma[nomagic]
- predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
+ additional predicate revFlowAlias(NodeEx node, FlowState state, Ap ap, Configuration config) {
revFlow(node, state, ap, config)
}
@@ -1700,7 +1702,7 @@ private module MkStage {
)
}
- predicate consCand(TypedContent tc, Ap ap, Configuration config) {
+ additional predicate consCand(TypedContent tc, Ap ap, Configuration config) {
revConsCand(tc, ap, config) and
validAp(ap, config)
}
@@ -1742,7 +1744,7 @@ private module MkStage {
)
}
- predicate stats(
+ additional predicate stats(
boolean fwd, int nodes, int fields, int conscand, int states, int tuples, Configuration config
) {
fwd = true and
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll
index e4fc3a01aa5..5c16102b066 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll
@@ -463,11 +463,7 @@ module Private {
c.asSummarizedCallable() = sc and pos = pos_
}
- Type getTypeImpl() {
- result = sc.getParameter(pos_).getType()
- or
- pos_ = -1 and result = sc.getDeclaringType()
- }
+ Type getTypeImpl() { result = sc.getParameterType(pos_) }
}
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
index d3a833d2438..6888899079c 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
@@ -83,6 +83,8 @@ predicate jumpStep(Node node1, Node node2) {
or
any(AdditionalValueStep a).step(node1, node2) and
node1.getEnclosingCallable() != node2.getEnclosingCallable()
+ or
+ FlowSummaryImpl::Private::Steps::summaryJumpStep(node1, node2)
}
/**
@@ -241,12 +243,6 @@ class DataFlowCallable extends TDataFlowCallable {
Field asFieldScope() { this = TFieldScope(result) }
- RefType getDeclaringType() {
- result = this.asCallable().getDeclaringType() or
- result = this.asSummarizedCallable().getDeclaringType() or
- result = this.asFieldScope().getDeclaringType()
- }
-
string toString() {
result = this.asCallable().toString() or
result = "Synthetic: " + this.asSummarizedCallable().toString() or
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
index a13c7cd1224..275569b4c02 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
@@ -61,6 +61,20 @@ module Public {
/** Gets a summary component for a return of kind `rk`. */
SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) }
+
+ /** Gets a summary component for synthetic global `sg`. */
+ SummaryComponent syntheticGlobal(SyntheticGlobal sg) {
+ result = TSyntheticGlobalSummaryComponent(sg)
+ }
+
+ /**
+ * A synthetic global. This represents some form of global state, which
+ * summaries can read and write individually.
+ */
+ abstract class SyntheticGlobal extends string {
+ bindingset[this]
+ SyntheticGlobal() { any() }
+ }
}
/**
@@ -256,6 +270,7 @@ module Private {
TParameterSummaryComponent(ArgumentPosition pos) or
TArgumentSummaryComponent(ParameterPosition pos) or
TReturnSummaryComponent(ReturnKind rk) or
+ TSyntheticGlobalSummaryComponent(SummaryComponent::SyntheticGlobal sg) or
TWithoutContentSummaryComponent(ContentSet c) or
TWithContentSummaryComponent(ContentSet c)
@@ -563,6 +578,11 @@ module Private {
getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.tail())), rk)
)
+ or
+ exists(SummaryComponent::SyntheticGlobal sg |
+ head = TSyntheticGlobalSummaryComponent(sg) and
+ result = getSyntheticGlobalType(sg)
+ )
)
or
n = summaryNodeOutputState(c, s) and
@@ -582,6 +602,11 @@ module Private {
getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.tail())), pos)
)
+ or
+ exists(SummaryComponent::SyntheticGlobal sg |
+ head = TSyntheticGlobalSummaryComponent(sg) and
+ result = getSyntheticGlobalType(sg)
+ )
)
)
}
@@ -692,6 +717,18 @@ module Private {
)
}
+ /**
+ * Holds if there is a jump step from `pred` to `succ`, which is synthesized
+ * from a flow summary.
+ */
+ predicate summaryJumpStep(Node pred, Node succ) {
+ exists(SummaryComponentStack s |
+ s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and
+ pred = summaryNodeOutputState(_, s) and
+ succ = summaryNodeInputState(_, s)
+ )
+ }
+
/**
* Holds if values stored inside content `c` are cleared at `n`. `n` is a
* synthesized summary node, so in order for values to be cleared at calls
@@ -871,18 +908,28 @@ module Private {
AccessPathRange() { relevantSpec(this) }
}
- /** Holds if specification component `c` parses as parameter `n`. */
+ /** Holds if specification component `token` parses as parameter `pos`. */
predicate parseParam(AccessPathToken token, ArgumentPosition pos) {
token.getName() = "Parameter" and
pos = parseParamBody(token.getAnArgument())
}
- /** Holds if specification component `c` parses as argument `n`. */
+ /** Holds if specification component `token` parses as argument `pos`. */
predicate parseArg(AccessPathToken token, ParameterPosition pos) {
token.getName() = "Argument" and
pos = parseArgBody(token.getAnArgument())
}
+ /** Holds if specification component `token` parses as synthetic global `sg`. */
+ predicate parseSynthGlobal(AccessPathToken token, string sg) {
+ token.getName() = "SyntheticGlobal" and
+ sg = token.getAnArgument()
+ }
+
+ private class SyntheticGlobalFromAccessPath extends SummaryComponent::SyntheticGlobal {
+ SyntheticGlobalFromAccessPath() { parseSynthGlobal(_, this) }
+ }
+
private SummaryComponent interpretComponent(AccessPathToken token) {
exists(ParameterPosition pos |
parseArg(token, pos) and result = SummaryComponent::argument(pos)
@@ -894,6 +941,10 @@ module Private {
or
token = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind())
or
+ exists(string sg |
+ parseSynthGlobal(token, sg) and result = SummaryComponent::syntheticGlobal(sg)
+ )
+ or
result = interpretComponentSpecific(token)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
index 1dfca1e6c4c..87cb0dace0e 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
@@ -9,12 +9,9 @@ private import DataFlowUtil
private import FlowSummaryImpl::Private
private import FlowSummaryImpl::Public
private import semmle.code.java.dataflow.ExternalFlow
+private import semmle.code.java.dataflow.FlowSummary as FlowSummary
-private module FlowSummaries {
- private import semmle.code.java.dataflow.FlowSummary as F
-}
-
-class SummarizedCallableBase = Callable;
+class SummarizedCallableBase = FlowSummary::SummarizedCallableBase;
DataFlowCallable inject(SummarizedCallable c) { result.asSummarizedCallable() = c }
@@ -55,6 +52,12 @@ DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) {
exists(rk)
}
+/** Gets the type of synthetic global `sg`. */
+DataFlowType getSyntheticGlobalType(SummaryComponent::SyntheticGlobal sg) {
+ exists(sg) and
+ result instanceof TypeObject
+}
+
bindingset[provenance]
private boolean isGenerated(string provenance) {
provenance = "generated" and result = true
@@ -67,14 +70,16 @@ private boolean isGenerated(string provenance) {
* `input`, output specification `output`, kind `kind`, and a flag `generated`
* stating whether the summary is autogenerated.
*/
-predicate summaryElement(Callable c, string input, string output, string kind, boolean generated) {
+predicate summaryElement(
+ SummarizedCallableBase c, string input, string output, string kind, boolean generated
+) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string provenance
|
summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind, provenance) and
generated = isGenerated(provenance) and
- c = interpretElement(namespace, type, subtypes, name, signature, ext)
+ c.asCallable() = interpretElement(namespace, type, subtypes, name, signature, ext)
)
}
@@ -82,11 +87,11 @@ predicate summaryElement(Callable c, string input, string output, string kind, b
* Holds if a negative flow summary exists for `c`, which means that there is no
* flow through `c`. The flag `generated` states whether the summary is autogenerated.
*/
-predicate negativeSummaryElement(Callable c, boolean generated) {
+predicate negativeSummaryElement(SummarizedCallableBase c, boolean generated) {
exists(string namespace, string type, string name, string signature, string provenance |
negativeSummaryModel(namespace, type, name, signature, provenance) and
generated = isGenerated(provenance) and
- c = interpretElement(namespace, type, false, name, signature, "")
+ c.asCallable() = interpretElement(namespace, type, false, name, signature, "")
)
}
diff --git a/java/ql/lib/semmle/code/java/deadcode/DeadCode.qll b/java/ql/lib/semmle/code/java/deadcode/DeadCode.qll
index 8dbb9bb530e..edeb9e9dccf 100644
--- a/java/ql/lib/semmle/code/java/deadcode/DeadCode.qll
+++ b/java/ql/lib/semmle/code/java/deadcode/DeadCode.qll
@@ -304,6 +304,15 @@ class RootdefCallable extends Callable {
this.getAnAnnotation() instanceof OverrideAnnotation
or
this.hasModifier("override")
+ or
+ // Exclude generated callables, such as `...$default` ones extracted from Kotlin code.
+ this.isCompilerGenerated()
+ or
+ // Exclude Kotlin serialization constructors.
+ this.(Constructor)
+ .getParameterType(this.getNumberOfParameters() - 1)
+ .(RefType)
+ .hasQualifiedName("kotlinx.serialization.internal", "SerializationConstructorMarker")
}
}
diff --git a/java/ql/lib/semmle/code/java/frameworks/Stream.qll b/java/ql/lib/semmle/code/java/frameworks/Stream.qll
index 5f6dcf38f86..0c1347044c5 100644
--- a/java/ql/lib/semmle/code/java/frameworks/Stream.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/Stream.qll
@@ -1,6 +1,101 @@
/** Definitions related to `java.util.stream`. */
private import semmle.code.java.dataflow.ExternalFlow
+private import semmle.code.java.dataflow.FlowSummary
+
+private class CollectCall extends MethodAccess {
+ CollectCall() {
+ this.getMethod()
+ .getSourceDeclaration()
+ .hasQualifiedName("java.util.stream", "Stream", "collect")
+ }
+}
+
+private class Collector extends MethodAccess {
+ Collector() {
+ this.getMethod().getDeclaringType().hasQualifiedName("java.util.stream", "Collectors")
+ }
+
+ predicate hasName(string name) { this.getMethod().hasName(name) }
+}
+
+private class CollectToContainer extends SyntheticCallable {
+ CollectToContainer() { this = "java.util.stream.collect()+Collectors.[toList,...]" }
+
+ override Call getACall() {
+ result
+ .(CollectCall)
+ .getArgument(0)
+ .(Collector)
+ .hasName([
+ "maxBy", "minBy", "toCollection", "toList", "toSet", "toUnmodifiableList",
+ "toUnmodifiableSet"
+ ])
+ }
+
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ input = SummaryComponentStack::elementOf(SummaryComponentStack::qualifier()) and
+ output = SummaryComponentStack::elementOf(SummaryComponentStack::return()) and
+ preservesValue = true
+ }
+}
+
+private class CollectToJoining extends SyntheticCallable {
+ CollectToJoining() { this = "java.util.stream.collect()+Collectors.joining" }
+
+ override Call getACall() { result.(CollectCall).getArgument(0).(Collector).hasName("joining") }
+
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ input = SummaryComponentStack::elementOf(SummaryComponentStack::qualifier()) and
+ output = SummaryComponentStack::return() and
+ preservesValue = false
+ }
+
+ override Type getReturnType() { result instanceof TypeString }
+}
+
+private class CollectToGroupingBy extends SyntheticCallable {
+ CollectToGroupingBy() {
+ this = "java.util.stream.collect()+Collectors.[groupingBy(Function),...]"
+ }
+
+ override Call getACall() {
+ exists(Method m |
+ m = result.(CollectCall).getArgument(0).(Collector).getMethod() and
+ m.hasName(["groupingBy", "groupingByConcurrent", "partitioningBy"]) and
+ m.getNumberOfParameters() = 1
+ )
+ }
+
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ input = SummaryComponentStack::elementOf(SummaryComponentStack::qualifier()) and
+ output =
+ SummaryComponentStack::elementOf(SummaryComponentStack::mapValueOf(SummaryComponentStack::return())) and
+ preservesValue = true
+ }
+}
+
+private class RequiredComponentStackForCollect extends RequiredSummaryComponentStack {
+ override predicate required(SummaryComponent head, SummaryComponentStack tail) {
+ head = SummaryComponent::element() and
+ tail = SummaryComponentStack::qualifier()
+ or
+ head = SummaryComponent::element() and
+ tail = SummaryComponentStack::return()
+ or
+ head = SummaryComponent::element() and
+ tail = SummaryComponentStack::mapValueOf(SummaryComponentStack::return())
+ or
+ head = SummaryComponent::mapValue() and
+ tail = SummaryComponentStack::return()
+ }
+}
private class StreamModel extends SummaryModelCsv {
override predicate row(string s) {
@@ -19,7 +114,7 @@ private class StreamModel extends SummaryModelCsv {
"java.util.stream;Stream;true;collect;(Supplier,BiConsumer,BiConsumer);;Argument[1].Parameter[0];Argument[2].Parameter[0..1];value;manual",
"java.util.stream;Stream;true;collect;(Supplier,BiConsumer,BiConsumer);;Argument[2].Parameter[0..1];Argument[1].Parameter[0];value;manual",
"java.util.stream;Stream;true;collect;(Supplier,BiConsumer,BiConsumer);;Argument[-1].Element;Argument[1].Parameter[1];value;manual",
- // Missing: collect(Collector collector)
+ // collect(Collector collector) is handled separately on a case-by-case basis as it is too complex for MaD
"java.util.stream;Stream;true;concat;(Stream,Stream);;Argument[0..1].Element;ReturnValue.Element;value;manual",
"java.util.stream;Stream;true;distinct;();;Argument[-1].Element;ReturnValue.Element;value;manual",
"java.util.stream;Stream;true;dropWhile;(Predicate);;Argument[-1].Element;Argument[0].Parameter[0];value;manual",
diff --git a/java/ql/lib/semmle/code/java/security/UnsafeContentUriResolution.qll b/java/ql/lib/semmle/code/java/security/UnsafeContentUriResolution.qll
new file mode 100644
index 00000000000..9a12d075e46
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/UnsafeContentUriResolution.qll
@@ -0,0 +1,87 @@
+/** Provides classes to reason about vulnerabilites related to content URIs. */
+
+import java
+private import semmle.code.java.dataflow.TaintTracking
+private import semmle.code.java.frameworks.android.Android
+private import semmle.code.java.security.PathSanitizer
+
+/** A URI that gets resolved by a `ContentResolver`. */
+abstract class ContentUriResolutionSink extends DataFlow::Node { }
+
+/** A sanitizer for content URIs. */
+abstract class ContentUriResolutionSanitizer extends DataFlow::Node { }
+
+/**
+ * A unit class for adding additional taint steps to configurations related to
+ * content URI resolution vulnerabilities.
+ */
+class ContentUriResolutionAdditionalTaintStep extends Unit {
+ /** Holds if the step from `node1` to `node2` should be considered an additional taint step. */
+ abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
+}
+
+/** The URI argument of a call to a `ContentResolver` URI-opening method. */
+private class DefaultContentUriResolutionSink extends ContentUriResolutionSink {
+ DefaultContentUriResolutionSink() {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof UriOpeningContentResolverMethod and
+ this.asExpr() = ma.getAnArgument() and
+ this.getType().(RefType).hasQualifiedName("android.net", "Uri")
+ )
+ }
+}
+
+/** A `ContentResolver` method that resolves a URI. */
+private class UriOpeningContentResolverMethod extends Method {
+ UriOpeningContentResolverMethod() {
+ this.hasName([
+ "openInputStream", "openOutputStream", "openAssetFile", "openAssetFileDescriptor",
+ "openFile", "openFileDescriptor", "openTypedAssetFile", "openTypedAssetFileDescriptor",
+ ]) and
+ this.getDeclaringType() instanceof AndroidContentResolver
+ }
+}
+
+private class UninterestingTypeSanitizer extends ContentUriResolutionSanitizer {
+ UninterestingTypeSanitizer() {
+ this.getType() instanceof BoxedType or
+ this.getType() instanceof PrimitiveType or
+ this.getType() instanceof NumberType
+ }
+}
+
+private class PathSanitizer extends ContentUriResolutionSanitizer instanceof PathInjectionSanitizer {
+}
+
+private class FilenameOnlySanitizer extends ContentUriResolutionSanitizer {
+ FilenameOnlySanitizer() {
+ exists(Method m | this.asExpr().(MethodAccess).getMethod() = m |
+ m.hasQualifiedName("java.io", "File", "getName") or
+ m.hasQualifiedName("kotlin.io", "FilesKt", ["getNameWithoutExtension", "getExtension"]) or
+ m.hasQualifiedName("org.apache.commons.io", "FilenameUtils", "getName")
+ )
+ }
+}
+
+/**
+ * A `ContentUriResolutionSink` that flows to an image-decoding function.
+ * Such functions raise exceptions when the input is not a valid image,
+ * which prevents accessing arbitrary non-image files.
+ */
+private class DecodedAsAnImageSanitizer extends ContentUriResolutionSanitizer {
+ DecodedAsAnImageSanitizer() {
+ exists(Argument decodeArg, MethodAccess decode |
+ decode.getArgument(0) = decodeArg and
+ decode
+ .getMethod()
+ .hasQualifiedName("android.graphics", "BitmapFactory",
+ [
+ "decodeByteArray", "decodeFile", "decodeFileDescriptor", "decodeResource",
+ "decodeStream"
+ ])
+ |
+ TaintTracking::localExprTaint(this.(ContentUriResolutionSink).asExpr().(Argument).getCall(),
+ decodeArg)
+ )
+ }
+}
diff --git a/java/ql/lib/semmle/code/java/security/UnsafeContentUriResolutionQuery.qll b/java/ql/lib/semmle/code/java/security/UnsafeContentUriResolutionQuery.qll
new file mode 100644
index 00000000000..b362a5dceeb
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/UnsafeContentUriResolutionQuery.qll
@@ -0,0 +1,24 @@
+/** Provides taint tracking configurations to be used in unsafe content URI resolution queries. */
+
+import java
+import semmle.code.java.dataflow.ExternalFlow
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.security.UnsafeContentUriResolution
+
+/** A taint-tracking configuration to find paths from remote sources to content URI resolutions. */
+class UnsafeContentResolutionConf extends TaintTracking::Configuration {
+ UnsafeContentResolutionConf() { this = "UnsafeContentResolutionConf" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof ContentUriResolutionSink }
+
+ override predicate isSanitizer(DataFlow::Node sanitizer) {
+ sanitizer instanceof ContentUriResolutionSanitizer
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
+ any(ContentUriResolutionAdditionalTaintStep s).step(node1, node2)
+ }
+}
diff --git a/java/ql/lib/semmle/code/java/security/regexp/ExponentialBackTracking.qll b/java/ql/lib/semmle/code/java/security/regexp/ExponentialBackTracking.qll
index d006837466b..4a608890249 100644
--- a/java/ql/lib/semmle/code/java/security/regexp/ExponentialBackTracking.qll
+++ b/java/ql/lib/semmle/code/java/security/regexp/ExponentialBackTracking.qll
@@ -202,7 +202,7 @@ private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, Stat
//
// We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself.
// This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`.
- // The below code is therefore a heuritic, that only flags regular expressions such as `/(a*)*b/`,
+ // The below code is therefore a heuristic, that only flags regular expressions such as `/(a*)*b/`,
// and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently.
r1 = r2 and
q1 = q2 and
diff --git a/java/ql/lib/semmle/code/java/security/regexp/NfaUtils.qll b/java/ql/lib/semmle/code/java/security/regexp/NfaUtils.qll
index 5112bdad11e..a6e4db6764e 100644
--- a/java/ql/lib/semmle/code/java/security/regexp/NfaUtils.qll
+++ b/java/ql/lib/semmle/code/java/security/regexp/NfaUtils.qll
@@ -59,8 +59,8 @@ predicate matchesEpsilon(RegExpTerm t) {
/**
* A lookahead/lookbehind that matches the empty string.
*/
-class EmptyPositiveSubPatttern extends RegExpSubPattern {
- EmptyPositiveSubPatttern() {
+class EmptyPositiveSubPattern extends RegExpSubPattern {
+ EmptyPositiveSubPattern() {
(
this instanceof RegExpPositiveLookahead
or
@@ -70,6 +70,9 @@ class EmptyPositiveSubPatttern extends RegExpSubPattern {
}
}
+/** DEPRECATED: Use `EmptyPositiveSubPattern` instead. */
+deprecated class EmptyPositiveSubPatttern = EmptyPositiveSubPattern;
+
/**
* A branch in a disjunction that is the root node in a literal, or a literal
* whose root node is not a disjunction.
@@ -133,7 +136,7 @@ private predicate isCanonicalTerm(RelevantRegExpTerm term, string str) {
}
/**
- * Gets a string reperesentation of the flags used with the regular expression.
+ * Gets a string representation of the flags used with the regular expression.
* Only the flags that are relevant for the canonicalization are included.
*/
string getCanonicalizationFlags(RegExpTerm root) {
@@ -334,7 +337,7 @@ private module CharacterClasses {
)
}
- private string lowercaseLetter() { result = "abdcefghijklmnopqrstuvwxyz".charAt(_) }
+ private string lowercaseLetter() { result = "abcdefghijklmnopqrstuvwxyz".charAt(_) }
private string upperCaseLetter() { result = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(_) }
@@ -697,9 +700,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
lbl = Epsilon() and q2 = Accept(getRoot(dollar))
)
or
- exists(EmptyPositiveSubPatttern empty | q1 = before(empty) |
- lbl = Epsilon() and q2 = after(empty)
- )
+ exists(EmptyPositiveSubPattern empty | q1 = before(empty) | lbl = Epsilon() and q2 = after(empty))
}
/**
@@ -1028,7 +1029,7 @@ module ReDoSPruning {
* as the suffix "X" will cause both the regular expressions to be rejected.
*
* The string `w` is repeated any number of times because it needs to be
- * infinitely repeatedable for the attack to work.
+ * infinitely repeatable for the attack to work.
* For the regular expression `/((ab)+)*abab/` the accepting state is not reachable from the fork
* using epsilon transitions. But any attempt at repeating `w` will end in a state that accepts all suffixes.
*/
diff --git a/java/ql/lib/semmle/code/java/security/regexp/SuperlinearBackTracking.qll b/java/ql/lib/semmle/code/java/security/regexp/SuperlinearBackTracking.qll
index c818e89ffa6..14a69dc0644 100644
--- a/java/ql/lib/semmle/code/java/security/regexp/SuperlinearBackTracking.qll
+++ b/java/ql/lib/semmle/code/java/security/regexp/SuperlinearBackTracking.qll
@@ -76,7 +76,7 @@ class StateTuple extends TStateTuple {
StateTuple() { this = MkStateTuple(q1, q2, q3) }
/**
- * Gest a string repesentation of this tuple.
+ * Gest a string representation of this tuple.
*/
string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" }
diff --git a/java/ql/lib/semmle/code/xml/AndroidManifest.qll b/java/ql/lib/semmle/code/xml/AndroidManifest.qll
index f53da67a650..c18b32751ed 100644
--- a/java/ql/lib/semmle/code/xml/AndroidManifest.qll
+++ b/java/ql/lib/semmle/code/xml/AndroidManifest.qll
@@ -180,6 +180,17 @@ class AndroidProviderXmlElement extends AndroidComponentXmlElement {
attr.getValue() = "true"
)
}
+
+ /**
+ * Holds if the provider element is only protected by either `android:readPermission` or `android:writePermission`.
+ */
+ predicate hasIncompletePermissions() {
+ (
+ this.getAnAttribute().(AndroidPermissionXmlAttribute).isWrite() or
+ this.getAnAttribute().(AndroidPermissionXmlAttribute).isRead()
+ ) and
+ not this.requiresPermissions()
+ }
}
/**
diff --git a/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/old.dbscheme b/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/old.dbscheme
new file mode 100644
index 00000000000..ecb42310286
--- /dev/null
+++ b/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/old.dbscheme
@@ -0,0 +1,1240 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes(
+ unique int id: @class,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @class ref
+);
+
+file_class(
+ int id: @class ref
+);
+
+class_object(
+ unique int id: @class ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @class ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isRecord(
+ unique int id: @class ref
+);
+
+interfaces(
+ unique int id: @interface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @interface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @interface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @class ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @class ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @interface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterface = @interface | @class;
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @class | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @class | @interface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar ;
+
+@member_modifiable = @class | @interface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @class ref
+)
diff --git a/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/semmlecode.dbscheme b/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/semmlecode.dbscheme
new file mode 100644
index 00000000000..709f1d1fd04
--- /dev/null
+++ b/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/semmlecode.dbscheme
@@ -0,0 +1,1240 @@
+/**
+ * An invocation of the compiler. Note that more than one file may be
+ * compiled per invocation. For example, this command compiles three
+ * source files:
+ *
+ * javac A.java B.java C.java
+ *
+ * The `id` simply identifies the invocation, while `cwd` is the working
+ * directory from which the compiler was invoked.
+ */
+compilations(
+ /**
+ * An invocation of the compiler. Note that more than one file may
+ * be compiled per invocation. For example, this command compiles
+ * three source files:
+ *
+ * javac A.java B.java C.java
+ */
+ unique int id : @compilation,
+ int kind: int ref,
+ string cwd : string ref,
+ string name : string ref
+);
+
+case @compilation.kind of
+ 1 = @javacompilation
+| 2 = @kotlincompilation
+;
+
+compilation_started(
+ int id : @compilation ref
+)
+
+/**
+ * The arguments that were passed to the extractor for a compiler
+ * invocation. If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then typically there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | *path to extractor*
+ * 1 | `--javac-args`
+ * 2 | A.java
+ * 3 | B.java
+ * 4 | C.java
+ */
+#keyset[id, num]
+compilation_args(
+ int id : @compilation ref,
+ int num : int ref,
+ string arg : string ref
+);
+
+/**
+ * The source files that are compiled by a compiler invocation.
+ * If `id` is for the compiler invocation
+ *
+ * javac A.java B.java C.java
+ *
+ * then there will be rows for
+ *
+ * num | arg
+ * --- | ---
+ * 0 | A.java
+ * 1 | B.java
+ * 2 | C.java
+ */
+#keyset[id, num]
+compilation_compiling_files(
+ int id : @compilation ref,
+ int num : int ref,
+ int file : @file ref
+);
+
+/**
+ * For each file recorded in `compilation_compiling_files`,
+ * there will be a corresponding row in
+ * `compilation_compiling_files_completed` once extraction
+ * of that file is complete. The `result` will indicate the
+ * extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+#keyset[id, num]
+compilation_compiling_files_completed(
+ int id : @compilation ref,
+ int num : int ref,
+ int result : int ref
+);
+
+/**
+ * The time taken by the extractor for a compiler invocation.
+ *
+ * For each file `num`, there will be rows for
+ *
+ * kind | seconds
+ * ---- | ---
+ * 1 | CPU seconds used by the extractor frontend
+ * 2 | Elapsed seconds during the extractor frontend
+ * 3 | CPU seconds used by the extractor backend
+ * 4 | Elapsed seconds during the extractor backend
+ */
+#keyset[id, num, kind]
+compilation_time(
+ int id : @compilation ref,
+ int num : int ref,
+ /* kind:
+ 1 = frontend_cpu_seconds
+ 2 = frontend_elapsed_seconds
+ 3 = extractor_cpu_seconds
+ 4 = extractor_elapsed_seconds
+ */
+ int kind : int ref,
+ float seconds : float ref
+);
+
+/**
+ * An error or warning generated by the extractor.
+ * The diagnostic message `diagnostic` was generated during compiler
+ * invocation `compilation`, and is the `file_number_diagnostic_number`th
+ * message generated while extracting the `file_number`th file of that
+ * invocation.
+ */
+#keyset[compilation, file_number, file_number_diagnostic_number]
+diagnostic_for(
+ unique int diagnostic : @diagnostic ref,
+ int compilation : @compilation ref,
+ int file_number : int ref,
+ int file_number_diagnostic_number : int ref
+);
+
+/**
+ * The `cpu_seconds` and `elapsed_seconds` are the CPU time and elapsed
+ * time (respectively) that the original compilation (not the extraction)
+ * took for compiler invocation `id`.
+ */
+compilation_compiler_times(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref
+);
+
+/**
+ * If extraction was successful, then `cpu_seconds` and
+ * `elapsed_seconds` are the CPU time and elapsed time (respectively)
+ * that extraction took for compiler invocation `id`.
+ * The `result` will indicate the extraction result:
+ *
+ * 0: Successfully extracted
+ * 1: Errors were encountered, but extraction recovered
+ * 2: Errors were encountered, and extraction could not recover
+ */
+compilation_finished(
+ unique int id : @compilation ref,
+ float cpu_seconds : float ref,
+ float elapsed_seconds : float ref,
+ int result : int ref
+);
+
+diagnostics(
+ unique int id: @diagnostic,
+ string generated_by: string ref, // TODO: Sync this with the other languages?
+ int severity: int ref,
+ string error_tag: string ref,
+ string error_message: string ref,
+ string full_error_message: string ref,
+ int location: @location_default ref
+);
+
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * SMAP
+ */
+
+smap_header(
+ int outputFileId: @file ref,
+ string outputFilename: string ref,
+ string defaultStratum: string ref
+);
+
+smap_files(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ string inputFileName: string ref,
+ int inputFileId: @file ref
+);
+
+smap_lines(
+ int outputFileId: @file ref,
+ string stratum: string ref,
+ int inputFileNum: int ref,
+ int inputStartLine: int ref,
+ int inputLineCount: int ref,
+ int outputStartLine: int ref,
+ int outputLineIncrement: int ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+/**
+ * An errortype is used when the extractor is unable to extract a type
+ * correctly for some reason.
+ */
+error_type(
+ unique int id: @errortype
+);
+
+classes(
+ unique int id: @class,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @class ref
+);
+
+file_class(
+ int id: @class ref
+);
+
+class_object(
+ unique int id: @class ref,
+ unique int instance: @field ref
+);
+
+type_companion_object(
+ unique int id: @classorinterface ref,
+ unique int instance: @field ref,
+ unique int companion_object: @class ref
+);
+
+kt_nullable_types(
+ unique int id: @kt_nullable_type,
+ int classid: @reftype ref
+)
+
+kt_notnull_types(
+ unique int id: @kt_notnull_type,
+ int classid: @reftype ref
+)
+
+kt_type_alias(
+ unique int id: @kt_type_alias,
+ string name: string ref,
+ int kttypeid: @kt_type ref
+)
+
+@kt_type = @kt_nullable_type | @kt_notnull_type
+
+isRecord(
+ unique int id: @class ref
+);
+
+interfaces(
+ unique int id: @interface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @interface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+fieldsKotlinType(
+ unique int id: @field ref,
+ int kttypeid: @kt_type ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+constrsKotlinType(
+ unique int id: @constructor ref,
+ int kttypeid: @kt_type ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+methodsKotlinType(
+ unique int id: @method ref,
+ int kttypeid: @kt_type ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramsKotlinType(
+ unique int id: @param ref,
+ int kttypeid: @kt_type ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @interface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @class ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @classorinterfaceorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @classorinterfaceorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @class ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[typeid] #keyset[parent]
+isLocalClassOrInterface(
+ int typeid: @classorinterface ref,
+ int parent: @localtypedeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @interface ref
+);
+
+permits(
+ int id1: @classorinterface ref,
+ int id2: @classorinterface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @classorinterfaceorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt | @switchexpr | @whenexpr| @stmtexpr;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localtypedeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+| 23 = @yieldstmt
+| 24 = @errorstmt
+| 25 = @whenbranch
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @exprparent ref,
+ int idx: int ref
+);
+
+exprsKotlinType(
+ unique int id: @expr ref,
+ int kttypeid: @kt_type ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+isParenthesized(
+ unique int id: @expr ref,
+ int parentheses: int ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr // deprecated
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+| 73 = @switchexpr
+| 74 = @errorexpr
+| 75 = @whenexpr
+| 76 = @getclassexpr
+| 77 = @safecastexpr
+| 78 = @implicitcastexpr
+| 79 = @implicitnotnullexpr
+| 80 = @implicitcoerciontounitexpr
+| 81 = @notinstanceofexpr
+| 82 = @stmtexpr
+| 83 = @stringtemplateexpr
+| 84 = @notnullexpr
+| 85 = @unsafecoerceexpr
+| 86 = @valueeqexpr
+| 87 = @valueneexpr
+| 88 = @propertyref
+;
+
+/** Holds if this `when` expression was written as an `if` expression. */
+when_if(unique int id: @whenexpr ref);
+
+/** Holds if this `when` branch was written as an `else` branch. */
+when_branch_else(unique int id: @whenbranch ref);
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref | @propertyref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr
+ | @valueeqexpr
+ | @valueneexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr
+ | @notnullexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+propertyRefGetBinding(
+ unique int id: @expr ref,
+ int getter: @callable ref
+);
+
+propertyRefFieldBinding(
+ unique int id: @expr ref,
+ int field: @field ref
+);
+
+propertyRefSetBinding(
+ unique int id: @expr ref,
+ int setter: @callable ref
+);
+
+@exprparent = @stmt | @expr | @whenbranch | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+localvarsKotlinType(
+ unique int id: @localvar ref,
+ int kttypeid: @kt_type ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@classorinterface = @interface | @class;
+@classorinterfaceorpackage = @classorinterface | @package;
+@classorinterfaceorcallable = @classorinterface | @callable;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype | @errortype;
+@classorarray = @class | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+
+/** A program element that has a name. */
+@element = @package | @modifier | @annotation | @errortype |
+ @locatableElement;
+
+@locatableElement = @file | @primitive | @class | @interface | @method | @constructor | @param | @exception | @field |
+ @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl | @kt_type | @kt_type_alias |
+ @kt_property;
+
+@modifiable = @member_modifiable| @param | @localvar | @typevariable;
+
+@member_modifiable = @class | @interface | @method | @constructor | @field | @kt_property;
+
+@member = @method | @constructor | @field | @reftype ;
+
+/** A program element that has a location. */
+@locatable = @typebound | @javadoc | @javadocTag | @javadocText | @xmllocatable | @ktcomment |
+ @locatableElement;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/*
+ * configuration files with key value pairs
+ */
+
+configs(
+ unique int id: @config
+);
+
+configNames(
+ unique int id: @configName,
+ int config: @config ref,
+ string name: string ref
+);
+
+configValues(
+ unique int id: @configValue,
+ int config: @config ref,
+ string value: string ref
+);
+
+configLocations(
+ int locatable: @configLocatable ref,
+ int location: @location_default ref
+);
+
+@configLocatable = @config | @configName | @configValue;
+
+ktComments(
+ unique int id: @ktcomment,
+ int kind: int ref,
+ string text : string ref
+)
+
+ktCommentSections(
+ unique int id: @ktcommentsection,
+ int comment: @ktcomment ref,
+ string content : string ref
+)
+
+ktCommentSectionNames(
+ unique int id: @ktcommentsection ref,
+ string name : string ref
+)
+
+ktCommentSectionSubjectNames(
+ unique int id: @ktcommentsection ref,
+ string subjectname : string ref
+)
+
+#keyset[id, owner]
+ktCommentOwners(
+ int id: @ktcomment ref,
+ int owner: @top ref
+)
+
+ktExtensionFunctions(
+ unique int id: @method ref,
+ int typeid: @type ref,
+ int kttypeid: @kt_type ref
+)
+
+ktProperties(
+ unique int id: @kt_property,
+ string nodeName: string ref
+)
+
+ktPropertyGetters(
+ unique int id: @kt_property ref,
+ int getter: @method ref
+)
+
+ktPropertySetters(
+ unique int id: @kt_property ref,
+ int setter: @method ref
+)
+
+ktPropertyBackingFields(
+ unique int id: @kt_property ref,
+ int backingField: @field ref
+)
+
+ktSyntheticBody(
+ unique int id: @callable ref,
+ int kind: int ref
+ // 1: ENUM_VALUES
+ // 2: ENUM_VALUEOF
+)
+
+ktLocalFunction(
+ unique int id: @method ref
+)
+
+ktInitializerAssignment(
+ unique int id: @assignexpr ref
+)
+
+ktPropertyDelegates(
+ unique int id: @kt_property ref,
+ unique int variableId: @variable ref
+)
+
+/**
+ * If `id` is a compiler generated element, then the kind indicates the
+ * reason that the compiler generated it.
+ * See `Element.compilerGeneratedReason()` for an explanation of what
+ * each `kind` means.
+ */
+compiler_generated(
+ unique int id: @element ref,
+ int kind: int ref
+)
+
+ktFunctionOriginalNames(
+ unique int id: @method ref,
+ string name: string ref
+)
+
+ktDataClasses(
+ unique int id: @class ref
+)
diff --git a/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/upgrade.properties b/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/upgrade.properties
new file mode 100644
index 00000000000..9d0da86d308
--- /dev/null
+++ b/java/ql/lib/upgrades/ecb42310286011ada450ff65b9b417509863549f/upgrade.properties
@@ -0,0 +1,2 @@
+description: Make type parameters modifiable
+compatibility: backwards
diff --git a/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql
index 1ad81badd22..dc73fb8fcdb 100644
--- a/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql
+++ b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql
@@ -23,6 +23,8 @@ where
m.overrides(overridden) and
not m.hasModifier("override") and
not m.isOverrideAnnotated() and
- not exists(FunctionalExpr mref | mref.asMethod() = m)
+ not exists(FunctionalExpr mref | mref.asMethod() = m) and
+ // Ignore generated constructs, such as functions extracted from Kotlin code:
+ not m.isCompilerGenerated()
select m, "This method overrides $@; it is advisable to add an Override annotation.", overridden,
overridden.getDeclaringType() + "." + overridden.getName()
diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp
index 1c32e1cf952..7d4dfcf82a6 100644
--- a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp
+++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp
@@ -16,7 +16,7 @@ A bean definition is considered to be used if one or more of the following is tr
The bean is injected in to a constructor or method of a live bean due to autowiring. This includes
autowiring by annotation (@Autowired or @Inject), and autowiring configured
by the autowired attribute within bean configuration files.
-The bean is explictly loaded from a factory bean. It is not always possible to determine when
+ The bean is explicitly loaded from a factory bean. It is not always possible to determine when
this occurs, because factory beans are loaded using a String value, which may contain
arbitrary values.
The bean is called reflectively by the Spring framework. For example, if the class is a Spring
diff --git a/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLockingShared.inc.qhelp b/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLockingShared.inc.qhelp
index 274eb6b1608..28bdebf85b1 100644
--- a/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLockingShared.inc.qhelp
+++ b/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLockingShared.inc.qhelp
@@ -62,7 +62,7 @@ runtime.
The code above should be rewritten to both use volatile and finish
all initialization before f is updated. Additionally, a local
-variable can be used to avoid reading the field more times than neccessary.
+variable can be used to avoid reading the field more times than necessary.
diff --git a/java/ql/src/Security/CWE/CWE-200/TempDirLocalInformationDisclosure.qhelp b/java/ql/src/Security/CWE/CWE-200/TempDirLocalInformationDisclosure.qhelp
index e3bf61107c4..98a3b8d36da 100644
--- a/java/ql/src/Security/CWE/CWE-200/TempDirLocalInformationDisclosure.qhelp
+++ b/java/ql/src/Security/CWE/CWE-200/TempDirLocalInformationDisclosure.qhelp
@@ -43,7 +43,7 @@ For example: PosixFilePermissions.asFileAttribute(EnumSet.of(PosixFilePerm
+ When an Android application wants to access data in a content provider, it uses the ContentResolver
+ object. ContentResolvers communicate with an instance of a class that implements the
+ ContentProvider interface via URIs with the content:// scheme.
+
+ The authority part (the first path segment) of the URI, passed as parameter to the ContentResolver,
+ determines which content provider is contacted for the operation. Specific operations that act on files also
+ support the file:// scheme, in which case the local filesystem is queried instead.
+
+ If an external component, like a malicious or compromised application, controls the URI for a
+ ContentResolver operation, it can trick the vulnerable application into accessing its own private
+ files or non-exported content providers. The attacking application might be able to get access to the file by forcing it to be copied to a public directory, like
+ external storage, or tamper with the contents by making the application overwrite the file with unexpected data.
+
+ If possible, avoid using externally-provided data to determine the URI for a ContentResolver to use.
+ If that is not an option, validate that the incoming URI can only reference trusted components, like an allow list
+ of content providers and/or applications, or alternatively make sure that the URI does not reference private
+ directories like /data/.
+
+ This example shows three ways of opening a file using a ContentResolver. In the first case, externally-provided
+ data from an intent is used directly in the file-reading operation. This allows an attacker to provide a URI
+ of the form /data/data/(vulnerable app package)/(private file) to trick the application into reading it and
+ copying it to the external storage. In the second case, an insufficient check is performed on the externally-provided URI, still
+ leaving room for exploitation. In the third case, the URI is correctly validated before being used, making sure it does not reference
+ any internal application files.
+
-In the onReceive method of a BroadcastReciever, the action of the received Intent should be checked. The following code demonstrates this.
+In the onReceive method of a BroadcastReceiver, the action of the received Intent should be checked. The following code demonstrates this.
The Android manifest file specifies the content providers for the application
+using provider elements. The provider element
+specifies the explicit permissions an application requires in order to access a
+resource using that provider.
+
+ You specify the permissions using
+ the android:readPermission, android:writePermission,
+ or android:permission attributes.
+If you do not specify the permission required to perform an operation, the application will implicitly have access to perform that operation.
+ For example, if you specify only android:readPermission, the application must have explicit permission to read data, but requires no permission to write data.
+
To prevent permission bypass, you should create provider elements that either
+ specify both the android:readPermission
+ and android:writePermission attributes, or specify
+ the android:permission attribute.
+
In the following two (bad) examples, the provider is configured with only + read or write permissions. This allows a malicious application to bypass the permission check by requesting access to the unrestricted operation.
+ +In the following (good) examples, the provider is configured with full permissions, protecting it from a permissions bypass.
+ +- An unrestricted view name manipulation vulnerability in Spring Framework could lead to attacker-controlled arbitary SpEL expressions being evaluated using attacker-controlled data, which may in turn allow an attacker to run arbitrary code. + An unrestricted view name manipulation vulnerability in Spring Framework could lead to attacker-controlled arbitrary SpEL expressions being evaluated using attacker-controlled data, which may in turn allow an attacker to run arbitrary code.
Note: two related variants of this problem are detected by different queries, `java/spring-view-manipulation` and `java/spring-view-manipulation-implicit`. The first detects taint flow problems where the return types is always String. While the latter, `java/spring-view-manipulation-implicit` detects cases where the request mapping method has a non-string return type such as void.
@@ -19,7 +19,7 @@
In general, using user input to determine Spring view name should be avoided.
If user input must be included in the expression, the controller can be annotated by
- a @ReponseBody annotation. In this case, Spring Framework does not interpret
+ a @ResponseBody annotation. In this case, Spring Framework does not interpret
it as a view name, but just returns this string in HTTP Response. The same applies to using
a @RestController annotation on a class, as internally it inherits @ResponseBody.
- This can be easily prevented by using the ResponseBody annotation which marks the reponse is already processed preventing exploitation of Spring View Manipulation vulnerabilities. Alternatively, this can also be fixed by adding a HttpServletResponse parameter to the method definition as shown in the example below.
+ This can be easily prevented by using the ResponseBody annotation which marks the response is already processed preventing exploitation of Spring View Manipulation vulnerabilities. Alternatively, this can also be fixed by adding a HttpServletResponse parameter to the method definition as shown in the example below.
An application should not disable the default revocationg checking mechanism +
An application should not disable the default revocation checking mechanism unless it provides a custom revocation checker.
Transport Layer Security (TLS) provides a number of security features such as -confidentiality, integrity, replay prevention and authenticatin. +confidentiality, integrity, replay prevention and authentication. There are several versions of TLS protocols. The latest is TLS 1.3. Unfortunately, older versions were found to be vulnerable to a number of attacks.
An application should use TLS 1.3. Currenlty, TLS 1.2 is also considered acceptable.
+An application should use TLS 1.3. Currently, TLS 1.2 is also considered acceptable.
-Allowing users to freely choose the name of a class to instantiate could provide means to attack a vulnerable appplication. +Allowing users to freely choose the name of a class to instantiate could provide means to attack a vulnerable application.
-The next example shows how to set a deserilization filter for a remote object: +The next example shows how to set a deserialization filter for a remote object:
For special use cases some applications may implement a custom service which handles JMX-RMI connections.
-When creating such a custom service, a developer should pass a certain environment configuration to the JMX-RMI server initalisation, +
When creating such a custom service, a developer should pass a certain environment configuration to the JMX-RMI server initialization, as otherwise the JMX-RMI service is susceptible to an unsafe deserialization vulnerability.
This is because the JMX-RMI service allows attackers to supply arbitrary objects to the service authentication
@@ -41,11 +41,11 @@ For this reason an initialization with a null environment is also v
The first example shows how an JMX server is initialized securely with the JMXConnectorServerFactory.newJMXConnectorServer() call.
The second example shows how a JMX Server is initialized securely if the RMIConnectorServer class is used.