From 457e2eaf594f052af8e89375670fac54437074c8 Mon Sep 17 00:00:00 2001
From: Grzegorz Golawski
Date: Sun, 19 Apr 2020 20:31:57 +0200
Subject: [PATCH 001/734] CodeQL query to detect OGNL injections
---
.../Security/CWE/CWE-917/OgnlInjection.java | 17 +++
.../Security/CWE/CWE-917/OgnlInjection.qhelp | 34 ++++++
.../Security/CWE/CWE-917/OgnlInjection.ql | 22 ++++
.../Security/CWE/CWE-917/OgnlInjectionLib.qll | 109 ++++++++++++++++++
java/ql/src/experimental/qlpack.yml | 4 +
java/ql/test/experimental/qlpack.yml | 4 +
.../security/CWE-917/OgnlInjection.expected | 48 ++++++++
.../security/CWE-917/OgnlInjection.java | 41 +++++++
.../security/CWE-917/OgnlInjection.qlref | 1 +
.../query-tests/security/CWE-917/options | 1 +
.../stubs/ognl-3.2.14/ognl/JavaSource.java | 3 +
.../stubs/ognl-3.2.14/ognl/Node.java | 6 +
.../stubs/ognl-3.2.14/ognl/Ognl.java | 26 +++++
.../stubs/ognl-3.2.14/ognl/OgnlContext.java | 71 ++++++++++++
.../stubs/ognl-3.2.14/ognl/OgnlException.java | 3 +
.../web/bind/annotation/RequestParam.java | 8 ++
.../opensymphony/xwork2/ognl/OgnlUtil.java | 16 +++
17 files changed, 414 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
create mode 100644 java/ql/src/experimental/qlpack.yml
create mode 100644 java/ql/test/experimental/qlpack.yml
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-917/options
create mode 100644 java/ql/test/experimental/stubs/ognl-3.2.14/ognl/JavaSource.java
create mode 100644 java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Node.java
create mode 100644 java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Ognl.java
create mode 100644 java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlContext.java
create mode 100644 java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlException.java
create mode 100644 java/ql/test/experimental/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java
create mode 100644 java/ql/test/experimental/stubs/struts2-core-2.5.22/com/opensymphony/xwork2/ognl/OgnlUtil.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.java b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.java
new file mode 100644
index 00000000000..cc99ff46517
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.java
@@ -0,0 +1,17 @@
+import ognl.Ognl;
+import ognl.OgnlException;
+
+public void evaluate(HttpServletRequest request, Object root) throws OgnlException {
+ String expression = request.getParameter("expression");
+
+ // BAD: User provided expression is evaluated
+ Ognl.getValue(expression, root);
+
+ // GOOD: The name is validated and expression is evaluated in sandbox
+ System.setProperty("ognl.security.manager", ""); // Or add -Dognl.security.manager to JVM args
+ if (isValid(expression)) {
+ Ognl.getValue(expression, root);
+ } else {
+ // Reject the request
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp
new file mode 100644
index 00000000000..66d04e41433
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp
@@ -0,0 +1,34 @@
+
+
+
+Object-Graph Navigation Language (OGNL) is an open-source Expression Language (EL) for Java. Due
+to its ability to create or change executable code, OGNL is capable of introducing critical
+security flaws to any application that uses it. Evaluation of unvalidated expressions can let
+attacker to modify Java objects' properties or execute arbitrary code.
+
+
+
+The general recommendation is to not evaluate untrusted ONGL expressions. If user provided OGNL
+expressions must be evaluated, do this in sandbox (add `-Dognl.security.manager` to JVM arguments)
+and validate the expressions before evaluation.
+
+
+
+In the following examples, the code accepts an OGNL expression from the user and evaluates it.
+
+
+In the first example, the user provided OGNL expression is parsed and evaluated.
+
+The second example validates the expression and evaluates it inside the sandbox.
+
+
+
+
+
+Oracle: Java Naming and Directory Interface (JNDI).
+Black Hat materials: A Journey from JNDI/LDAP Manipulation to Remote Code Execution Dream Land.
+Veracode: Exploiting JNDI Injections in Java.
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.ql
new file mode 100644
index 00000000000..e8a75591b98
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.ql
@@ -0,0 +1,22 @@
+/**
+ * @name OGNL Expression Language statement with user-controlled input
+ * @description Evaluation of OGNL Expression Language statement with user-controlled input can
+ * lead to execution of arbitrary code.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/ognl-injection
+ * @tags security
+ * external/cwe/cwe-917
+ */
+
+import java
+import semmle.code.java.dataflow.FlowSources
+import DataFlow
+import DataFlow::PathGraph
+import OgnlInjectionLib
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, OgnlInjectionFlowConfig conf
+where conf.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "OGNL expression might include input from $@.",
+ source.getNode(), "this user input"
diff --git a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
new file mode 100644
index 00000000000..faac234574b
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
@@ -0,0 +1,109 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+import DataFlow
+import DataFlow::PathGraph
+
+/**
+ * A taint-tracking configuration for unvalidated user input that is used in OGNL EL evaluation.
+ */
+class OgnlInjectionFlowConfig extends TaintTracking::Configuration {
+ OgnlInjectionFlowConfig() { this = "OgnlInjectionFlowConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof OgnlInjectionSink }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
+ parseCompileExpressionStep(node1, node2)
+ }
+}
+
+/** The class `org.apache.commons.ognl.Ognl` or `ognl.Ognl`. */
+class TypeOgnl extends Class {
+ TypeOgnl() {
+ this.hasQualifiedName("org.apache.commons.ognl", "Ognl") or
+ this.hasQualifiedName("ognl", "Ognl")
+ }
+}
+
+/** The interface `org.apache.commons.ognl.Node` or `ognl.Node`. */
+class TypeNode extends Interface {
+ TypeNode() {
+ this.hasQualifiedName("org.apache.commons.ognl", "Node") or
+ this.hasQualifiedName("ognl", "Node")
+ }
+}
+
+/** The class `com.opensymphony.xwork2.ognl.OgnlUtil`. */
+class TypeOgnlUtil extends Class {
+ TypeOgnlUtil() { this.hasQualifiedName("com.opensymphony.xwork2.ognl", "OgnlUtil") }
+}
+
+/**
+ * OGNL sink for OGNL injection vulnerabilities, i.e. 1st argument to `getValue` or `setValue`
+ * method from `Ognl` or `getValue` or `setValue` method from `Node`.
+ */
+predicate ognlSinkMethod(Method m, int index) {
+ (
+ m.getDeclaringType() instanceof TypeOgnl and index = 0
+ or
+ m.getDeclaringType().getAnAncestor*() instanceof TypeNode
+ ) and
+ (
+ m.hasName("getValue") or
+ m.hasName("setValue")
+ ) and
+ index = 0
+}
+
+/**
+ * Struts sink for OGNL injection vulnerabilities, i.e. 1st argument to `getValue`, `setValue` or
+ * `callMethod` method from `OgnlUtil`.
+ */
+predicate strutsSinkMethod(Method m, int index) {
+ m.getDeclaringType() instanceof TypeOgnlUtil and
+ (
+ m.hasName("getValue") or
+ m.hasName("setValue") or
+ m.hasName("callMethod")
+ ) and
+ index = 0
+}
+
+/** Holds if parameter at index `index` in method `m` is OGNL injection sink. */
+predicate ognlInjectionSinkMethod(Method m, int index) {
+ ognlSinkMethod(m, index) or
+ strutsSinkMethod(m, index)
+}
+
+/** A data flow sink for unvalidated user input that is used in OGNL EL evaluation. */
+class OgnlInjectionSink extends DataFlow::ExprNode {
+ OgnlInjectionSink() {
+ exists(MethodAccess ma, Method m, int index |
+ ma.getMethod() = m and
+ (ma.getArgument(index) = this.getExpr() or ma.getQualifier() = this.getExpr()) and
+ ognlInjectionSinkMethod(m, index)
+ )
+ }
+}
+
+/**
+ * Holds if `n1` to `n2` is a dataflow step that converts between `String` and `Object` or `Node`,
+ * i.e. `Ognl.parseExpression(tainted)` or `Ognl.compileExpression(tainted)`.
+ */
+predicate parseCompileExpressionStep(ExprNode n1, ExprNode n2) {
+ exists(MethodAccess ma, Method m, int index |
+ n1.asExpr() = ma.getArgument(index) and
+ n2.asExpr() = ma and
+ ma.getMethod() = m and
+ m.getDeclaringType() instanceof TypeOgnl
+ |
+ m.hasName("parseExpression") and index = 0
+ or
+ m.hasName("compileExpression") and index = 2
+ )
+}
diff --git a/java/ql/src/experimental/qlpack.yml b/java/ql/src/experimental/qlpack.yml
new file mode 100644
index 00000000000..bf76fc1b736
--- /dev/null
+++ b/java/ql/src/experimental/qlpack.yml
@@ -0,0 +1,4 @@
+name: codeql-java-experimental
+version: 0.0.0
+libraryPathDependencies: codeql-java
+extractor: java
diff --git a/java/ql/test/experimental/qlpack.yml b/java/ql/test/experimental/qlpack.yml
new file mode 100644
index 00000000000..4b7a7635a6c
--- /dev/null
+++ b/java/ql/test/experimental/qlpack.yml
@@ -0,0 +1,4 @@
+name: codeql-java-experimental-tests
+version: 0.0.0
+libraryPathDependencies: codeql-java-experimental
+extractor: java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.expected
new file mode 100644
index 00000000000..e74636a2b61
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.expected
@@ -0,0 +1,48 @@
+edges
+| OgnlInjection.java:11:39:11:63 | expr : String | OgnlInjection.java:13:19:13:22 | tree |
+| OgnlInjection.java:11:39:11:63 | expr : String | OgnlInjection.java:14:19:14:22 | tree |
+| OgnlInjection.java:11:39:11:63 | expr : String | OgnlInjection.java:16:17:16:27 | (...)... : Object |
+| OgnlInjection.java:16:17:16:27 | (...)... : Object | OgnlInjection.java:17:5:17:8 | node |
+| OgnlInjection.java:16:17:16:27 | (...)... : Object | OgnlInjection.java:18:5:18:8 | node |
+| OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:23:19:23:22 | tree |
+| OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:24:19:24:22 | tree |
+| OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:26:5:26:8 | tree |
+| OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:27:5:27:8 | tree |
+| OgnlInjection.java:30:40:30:64 | expr : String | OgnlInjection.java:31:19:31:22 | expr |
+| OgnlInjection.java:30:40:30:64 | expr : String | OgnlInjection.java:32:19:32:22 | expr |
+| OgnlInjection.java:35:26:35:50 | expr : String | OgnlInjection.java:37:19:37:22 | expr |
+| OgnlInjection.java:35:26:35:50 | expr : String | OgnlInjection.java:38:19:38:22 | expr |
+| OgnlInjection.java:35:26:35:50 | expr : String | OgnlInjection.java:39:31:39:34 | expr |
+nodes
+| OgnlInjection.java:11:39:11:63 | expr : String | semmle.label | expr : String |
+| OgnlInjection.java:13:19:13:22 | tree | semmle.label | tree |
+| OgnlInjection.java:14:19:14:22 | tree | semmle.label | tree |
+| OgnlInjection.java:16:17:16:27 | (...)... : Object | semmle.label | (...)... : Object |
+| OgnlInjection.java:17:5:17:8 | node | semmle.label | node |
+| OgnlInjection.java:18:5:18:8 | node | semmle.label | node |
+| OgnlInjection.java:21:41:21:65 | expr : String | semmle.label | expr : String |
+| OgnlInjection.java:23:19:23:22 | tree | semmle.label | tree |
+| OgnlInjection.java:24:19:24:22 | tree | semmle.label | tree |
+| OgnlInjection.java:26:5:26:8 | tree | semmle.label | tree |
+| OgnlInjection.java:27:5:27:8 | tree | semmle.label | tree |
+| OgnlInjection.java:30:40:30:64 | expr : String | semmle.label | expr : String |
+| OgnlInjection.java:31:19:31:22 | expr | semmle.label | expr |
+| OgnlInjection.java:32:19:32:22 | expr | semmle.label | expr |
+| OgnlInjection.java:35:26:35:50 | expr : String | semmle.label | expr : String |
+| OgnlInjection.java:37:19:37:22 | expr | semmle.label | expr |
+| OgnlInjection.java:38:19:38:22 | expr | semmle.label | expr |
+| OgnlInjection.java:39:31:39:34 | expr | semmle.label | expr |
+#select
+| OgnlInjection.java:13:19:13:22 | tree | OgnlInjection.java:11:39:11:63 | expr : String | OgnlInjection.java:13:19:13:22 | tree | OGNL expression might include input from $@. | OgnlInjection.java:11:39:11:63 | expr | this user input |
+| OgnlInjection.java:14:19:14:22 | tree | OgnlInjection.java:11:39:11:63 | expr : String | OgnlInjection.java:14:19:14:22 | tree | OGNL expression might include input from $@. | OgnlInjection.java:11:39:11:63 | expr | this user input |
+| OgnlInjection.java:17:5:17:8 | node | OgnlInjection.java:11:39:11:63 | expr : String | OgnlInjection.java:17:5:17:8 | node | OGNL expression might include input from $@. | OgnlInjection.java:11:39:11:63 | expr | this user input |
+| OgnlInjection.java:18:5:18:8 | node | OgnlInjection.java:11:39:11:63 | expr : String | OgnlInjection.java:18:5:18:8 | node | OGNL expression might include input from $@. | OgnlInjection.java:11:39:11:63 | expr | this user input |
+| OgnlInjection.java:23:19:23:22 | tree | OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:23:19:23:22 | tree | OGNL expression might include input from $@. | OgnlInjection.java:21:41:21:65 | expr | this user input |
+| OgnlInjection.java:24:19:24:22 | tree | OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:24:19:24:22 | tree | OGNL expression might include input from $@. | OgnlInjection.java:21:41:21:65 | expr | this user input |
+| OgnlInjection.java:26:5:26:8 | tree | OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:26:5:26:8 | tree | OGNL expression might include input from $@. | OgnlInjection.java:21:41:21:65 | expr | this user input |
+| OgnlInjection.java:27:5:27:8 | tree | OgnlInjection.java:21:41:21:65 | expr : String | OgnlInjection.java:27:5:27:8 | tree | OGNL expression might include input from $@. | OgnlInjection.java:21:41:21:65 | expr | this user input |
+| OgnlInjection.java:31:19:31:22 | expr | OgnlInjection.java:30:40:30:64 | expr : String | OgnlInjection.java:31:19:31:22 | expr | OGNL expression might include input from $@. | OgnlInjection.java:30:40:30:64 | expr | this user input |
+| OgnlInjection.java:32:19:32:22 | expr | OgnlInjection.java:30:40:30:64 | expr : String | OgnlInjection.java:32:19:32:22 | expr | OGNL expression might include input from $@. | OgnlInjection.java:30:40:30:64 | expr | this user input |
+| OgnlInjection.java:37:19:37:22 | expr | OgnlInjection.java:35:26:35:50 | expr : String | OgnlInjection.java:37:19:37:22 | expr | OGNL expression might include input from $@. | OgnlInjection.java:35:26:35:50 | expr | this user input |
+| OgnlInjection.java:38:19:38:22 | expr | OgnlInjection.java:35:26:35:50 | expr : String | OgnlInjection.java:38:19:38:22 | expr | OGNL expression might include input from $@. | OgnlInjection.java:35:26:35:50 | expr | this user input |
+| OgnlInjection.java:39:31:39:34 | expr | OgnlInjection.java:35:26:35:50 | expr : String | OgnlInjection.java:39:31:39:34 | expr | OGNL expression might include input from $@. | OgnlInjection.java:35:26:35:50 | expr | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.java b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.java
new file mode 100644
index 00000000000..300583c6fc9
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.java
@@ -0,0 +1,41 @@
+import ognl.Node;
+import ognl.Ognl;
+
+import java.util.HashMap;
+
+import com.opensymphony.xwork2.ognl.OgnlUtil;
+
+import org.springframework.web.bind.annotation.RequestParam;
+
+public class OgnlInjection {
+ public void testOgnlParseExpression(@RequestParam String expr) throws Exception {
+ Object tree = Ognl.parseExpression(expr);
+ Ognl.getValue(tree, new HashMap<>(), new Object());
+ Ognl.setValue(tree, new HashMap<>(), new Object());
+
+ Node node = (Node) tree;
+ node.getValue(null, new Object());
+ node.setValue(null, new Object(), new Object());
+ }
+
+ public void testOgnlCompileExpression(@RequestParam String expr) throws Exception {
+ Node tree = Ognl.compileExpression(null, new Object(), expr);
+ Ognl.getValue(tree, new HashMap<>(), new Object());
+ Ognl.setValue(tree, new HashMap<>(), new Object());
+
+ tree.getValue(null, new Object());
+ tree.setValue(null, new Object(), new Object());
+ }
+
+ public void testOgnlDirectlyToGetSet(@RequestParam String expr) throws Exception {
+ Ognl.getValue(expr, new Object());
+ Ognl.setValue(expr, new Object(), new Object());
+ }
+
+ public void testStruts(@RequestParam String expr) throws Exception {
+ OgnlUtil ognl = new OgnlUtil();
+ ognl.getValue(expr, new HashMap<>(), new Object());
+ ognl.setValue(expr, new HashMap<>(), new Object(), new Object());
+ new OgnlUtil().callMethod(expr, new HashMap<>(), new Object());
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref
new file mode 100644
index 00000000000..b72d532c765
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-917/OgnlInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-917/options b/java/ql/test/experimental/query-tests/security/CWE-917/options
new file mode 100644
index 00000000000..c5767a074fa
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-917/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/springframework-5.2.3:${testdir}/../../../stubs/ognl-3.2.14:${testdir}/../../../stubs/struts2-core-2.5.22
diff --git a/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/JavaSource.java b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/JavaSource.java
new file mode 100644
index 00000000000..0ca2ecb43a7
--- /dev/null
+++ b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/JavaSource.java
@@ -0,0 +1,3 @@
+package ognl;
+
+public interface JavaSource {}
diff --git a/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Node.java b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Node.java
new file mode 100644
index 00000000000..56d58f24d07
--- /dev/null
+++ b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Node.java
@@ -0,0 +1,6 @@
+package ognl;
+
+public interface Node extends JavaSource {
+ public Object getValue(OgnlContext context, Object source) throws OgnlException;
+ public void setValue(OgnlContext context, Object target, Object value) throws OgnlException;
+}
diff --git a/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Ognl.java b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Ognl.java
new file mode 100644
index 00000000000..1aa67646f92
--- /dev/null
+++ b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/Ognl.java
@@ -0,0 +1,26 @@
+package ognl;
+
+import java.util.*;
+
+public abstract class Ognl {
+ public static Object parseExpression(String expression) throws OgnlException {
+ return new Object();
+ }
+
+ public static Object getValue(Object tree, Map context, Object root) throws OgnlException {
+ return new Object();
+ }
+
+ public static void setValue(Object tree, Object root, Object value) throws OgnlException {}
+
+ public static Node compileExpression(OgnlContext context, Object root, String expression)
+ throws Exception {
+ return null;
+ }
+
+ public static Object getValue(String expression, Object root) throws OgnlException {
+ return new Object();
+ }
+
+ public static void setValue(String expression, Object root, Object value) throws OgnlException {}
+}
diff --git a/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlContext.java b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlContext.java
new file mode 100644
index 00000000000..fad33eb8e80
--- /dev/null
+++ b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlContext.java
@@ -0,0 +1,71 @@
+package ognl;
+
+import java.util.*;
+
+public class OgnlContext extends Object implements Map {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return true;
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return true;
+ }
+
+ @Override
+ public Object get(Object key) {
+ return new Object();
+ }
+
+ @Override
+ public Object put(Object key, Object value) {
+ return new Object();
+ }
+
+ @Override
+ public Object remove(Object key) {
+ return new Object();
+ }
+
+ @Override
+ public void putAll(Map t) { }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public Set keySet() {
+ return new HashSet();
+ }
+
+ @Override
+ public Collection values() {
+ return new HashSet();
+ }
+
+ @Override
+ public Set entrySet() {
+ return new HashSet();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlException.java b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlException.java
new file mode 100644
index 00000000000..447515aa58f
--- /dev/null
+++ b/java/ql/test/experimental/stubs/ognl-3.2.14/ognl/OgnlException.java
@@ -0,0 +1,3 @@
+package ognl;
+
+public class OgnlException extends Exception {}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java b/java/ql/test/experimental/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java
new file mode 100644
index 00000000000..5ae52ad123f
--- /dev/null
+++ b/java/ql/test/experimental/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java
@@ -0,0 +1,8 @@
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.*;
+
+@Target(value=ElementType.PARAMETER)
+@Retention(value=RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequestParam { }
diff --git a/java/ql/test/experimental/stubs/struts2-core-2.5.22/com/opensymphony/xwork2/ognl/OgnlUtil.java b/java/ql/test/experimental/stubs/struts2-core-2.5.22/com/opensymphony/xwork2/ognl/OgnlUtil.java
new file mode 100644
index 00000000000..5b1d031cf21
--- /dev/null
+++ b/java/ql/test/experimental/stubs/struts2-core-2.5.22/com/opensymphony/xwork2/ognl/OgnlUtil.java
@@ -0,0 +1,16 @@
+package com.opensymphony.xwork2.ognl;
+
+import java.util.*;
+import ognl.OgnlException;
+
+public class OgnlUtil {
+ public Object getValue(final String name, final Map context, final Object root) throws OgnlException {
+ return new Object();
+ }
+
+ public void setValue(final String name, final Map context, final Object root, final Object value) throws OgnlException {}
+
+ public Object callMethod(final String name, final Map context, final Object root) throws OgnlException {
+ return new Object();
+ }
+}
\ No newline at end of file
From 40fcd4cbe5030ff53583b55628ac53bba85c5e76 Mon Sep 17 00:00:00 2001
From: Grzegorz Golawski
Date: Sun, 19 Apr 2020 20:49:07 +0200
Subject: [PATCH 002/734] Fix references
---
.../experimental/Security/CWE/CWE-917/OgnlInjection.qhelp | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp
index 66d04e41433..55ed5340c11 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjection.qhelp
@@ -27,8 +27,7 @@ and validate the expressions before evaluation.
-Oracle: Java Naming and Directory Interface (JNDI).
-Black Hat materials: A Journey from JNDI/LDAP Manipulation to Remote Code Execution Dream Land.
-Veracode: Exploiting JNDI Injections in Java.
+OGNL library: OGNL library.
+Struts security: Proactively protect from OGNL Expression Injections attacks.
From ce2d7fe04cfeb640303f6cda4cb067758a5497f2 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Fri, 24 Apr 2020 12:25:40 +0200
Subject: [PATCH 003/734] Python: Improve QLDoc for Arguments
---
python/ql/src/semmle/python/Function.qll | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll
index 3bf991b313e..d72656c4d12 100644
--- a/python/ql/src/semmle/python/Function.qll
+++ b/python/ql/src/semmle/python/Function.qll
@@ -258,7 +258,8 @@ class Parameter extends Parameter_ {
/** An expression that generates a callable object, either a function expression or a lambda */
abstract class CallableExpr extends Expr {
/**
- * Gets the parameters of this callable.
+ * Gets The default values and annotations (type-hints) for the arguments of this callable.
+ *
* This predicate is called getArgs(), rather than getParameters() for compatibility with Python's AST module.
*/
abstract Arguments getArgs();
@@ -314,7 +315,12 @@ class Lambda extends Lambda_, CallableExpr {
override Arguments getArgs() { result = Lambda_.super.getArgs() }
}
-/** The arguments in a function definition */
+/**
+ * The default values and annotations (type hints) for the arguments in a function definition.
+ *
+ * Annotations (PEP 3107) is a general mechanism for providing annotations for a function,
+ * that is generally only used for type hints today (PEP 484).
+ */
class Arguments extends Arguments_ {
Expr getASubExpression() {
result = this.getAKwDefault() or
From 96b36a7f0f49ce5a62946993512e786cae003519 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Fri, 24 Apr 2020 14:28:25 +0200
Subject: [PATCH 004/734] Python: Clean up some QLdocs
---
python/ql/src/semmle/python/Function.qll | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll
index d72656c4d12..9a45ce1ae74 100644
--- a/python/ql/src/semmle/python/Function.qll
+++ b/python/ql/src/semmle/python/Function.qll
@@ -243,13 +243,13 @@ class Parameter extends Parameter_ {
}
/**
- * Holds if this parameter is a 'varargs' parameter.
+ * Holds if this parameter is a "varargs" parameter.
* The `varargs` in `f(a, b, *varargs)`.
*/
predicate isVarargs() { exists(Function func | func.getVararg() = this) }
/**
- * Holds if this parameter is a 'kwargs' parameter.
+ * Holds if this parameter is a "kwargs" parameter.
* The `kwargs` in `f(a, b, **kwargs)`.
*/
predicate isKwargs() { exists(Function func | func.getKwarg() = this) }
@@ -296,7 +296,7 @@ class FunctionExpr extends FunctionExpr_, CallableExpr {
override Arguments getArgs() { result = FunctionExpr_.super.getArgs() }
}
-/** A lambda expression, such as lambda x:x*x */
+/** A lambda expression, such as `lambda x: x+1` */
class Lambda extends Lambda_, CallableExpr {
/** Gets the expression to the right of the colon in this lambda expression */
Expr getExpression() {
From 0cc8d4911211b27fa5439069c3130bec01b41193 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Fri, 24 Apr 2020 14:48:53 +0200
Subject: [PATCH 005/734] Python: Add tests for full Python 3 parameters syntax
Currently keyword-only parameters are not handled properly :(
---
.../functions/Function.getAChildNode.expected | 7 +++++++
.../functions/Function.getAChildNode.ql | 4 ++++
.../functions/Function.getArg.expected | 2 ++
.../functions/Function.getArg.ql | 4 ++++
.../functions/Function.getArgByName.expected | 2 ++
.../functions/Function.getArgByName.ql | 4 ++++
.../FunctionExpr.getASubExpression.expected | 7 +++++++
.../FunctionExpr.getASubExpression.ql | 4 ++++
.../FunctionExpr.getArgs.getADefault.expected | 2 ++
.../FunctionExpr.getArgs.getADefault.ql | 4 ++++
...unctionExpr.getArgs.getAKwDefault.expected | 1 +
.../FunctionExpr.getArgs.getAKwDefault.ql | 4 ++++
.../ql/test/3/library-tests/functions/test.py | 20 +++++++++++++++++++
.../parameters/Annotations.expected | 4 ++++
.../3/library-tests/parameters/Annotations.ql | 4 ++++
.../parameters/Defaults.expected | 2 ++
.../3/library-tests/parameters/Defaults.ql | 4 ++++
.../library-tests/parameters/Special.expected | 4 ++++
.../3/library-tests/parameters/Special.ql | 10 ++++++++++
.../test/3/library-tests/parameters/test.py | 20 +++++++++++++++++++
20 files changed, 113 insertions(+)
create mode 100644 python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
create mode 100644 python/ql/test/3/library-tests/functions/Function.getAChildNode.ql
create mode 100644 python/ql/test/3/library-tests/functions/Function.getArg.expected
create mode 100644 python/ql/test/3/library-tests/functions/Function.getArg.ql
create mode 100644 python/ql/test/3/library-tests/functions/Function.getArgByName.expected
create mode 100644 python/ql/test/3/library-tests/functions/Function.getArgByName.ql
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.ql
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql
create mode 100644 python/ql/test/3/library-tests/functions/test.py
create mode 100644 python/ql/test/3/library-tests/parameters/Annotations.expected
create mode 100644 python/ql/test/3/library-tests/parameters/Annotations.ql
create mode 100644 python/ql/test/3/library-tests/parameters/Defaults.expected
create mode 100644 python/ql/test/3/library-tests/parameters/Defaults.ql
create mode 100644 python/ql/test/3/library-tests/parameters/Special.expected
create mode 100644 python/ql/test/3/library-tests/parameters/Special.ql
create mode 100644 python/ql/test/3/library-tests/parameters/test.py
diff --git a/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected b/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
new file mode 100644
index 00000000000..20aec45af67
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
@@ -0,0 +1,7 @@
+| test.py:4:1:11:2 | Function func | test.py:5:5:5:12 | pos_only |
+| test.py:4:1:11:2 | Function func | test.py:7:5:7:10 | normal |
+| test.py:4:1:11:2 | Function func | test.py:8:6:8:9 | args |
+| test.py:4:1:11:2 | Function func | test.py:10:7:10:12 | kwargs |
+| test.py:4:1:11:2 | Function func | test.py:12:5:12:41 | ExprStmt |
+| test.py:4:1:11:2 | Function func | test.py:13:5:13:15 | ExprStmt |
+| test.py:4:1:11:2 | Function func | test.py:14:5:14:17 | ExprStmt |
diff --git a/python/ql/test/3/library-tests/functions/Function.getAChildNode.ql b/python/ql/test/3/library-tests/functions/Function.getAChildNode.ql
new file mode 100644
index 00000000000..222d3048119
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/Function.getAChildNode.ql
@@ -0,0 +1,4 @@
+import python
+
+from Function f
+select f, f.getAChildNode()
diff --git a/python/ql/test/3/library-tests/functions/Function.getArg.expected b/python/ql/test/3/library-tests/functions/Function.getArg.expected
new file mode 100644
index 00000000000..54575caa9ee
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/Function.getArg.expected
@@ -0,0 +1,2 @@
+| test.py:4:1:11:2 | Function func | 0 | test.py:5:5:5:12 | Parameter |
+| test.py:4:1:11:2 | Function func | 1 | test.py:7:5:7:10 | Parameter |
diff --git a/python/ql/test/3/library-tests/functions/Function.getArg.ql b/python/ql/test/3/library-tests/functions/Function.getArg.ql
new file mode 100644
index 00000000000..660a2714337
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/Function.getArg.ql
@@ -0,0 +1,4 @@
+import python
+
+from Function f, int i
+select f, i, f.getArg(i)
diff --git a/python/ql/test/3/library-tests/functions/Function.getArgByName.expected b/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
new file mode 100644
index 00000000000..d9bad185fd6
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
@@ -0,0 +1,2 @@
+| test.py:4:1:11:2 | Function func | normal | test.py:7:5:7:10 | Parameter |
+| test.py:4:1:11:2 | Function func | pos_only | test.py:5:5:5:12 | Parameter |
diff --git a/python/ql/test/3/library-tests/functions/Function.getArgByName.ql b/python/ql/test/3/library-tests/functions/Function.getArgByName.ql
new file mode 100644
index 00000000000..75f16f985a9
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/Function.getArgByName.ql
@@ -0,0 +1,4 @@
+import python
+
+from Function f, string name
+select f, name, f.getArgByName(name)
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
new file mode 100644
index 00000000000..76e5b5ab802
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
@@ -0,0 +1,7 @@
+| test.py:4:1:11:2 | FunctionExpr | test.py:5:15:5:17 | int |
+| test.py:4:1:11:2 | FunctionExpr | test.py:5:21:5:22 | UnaryExpr |
+| test.py:4:1:11:2 | FunctionExpr | test.py:7:13:7:15 | int |
+| test.py:4:1:11:2 | FunctionExpr | test.py:7:19:7:20 | UnaryExpr |
+| test.py:4:1:11:2 | FunctionExpr | test.py:8:12:8:23 | Str |
+| test.py:4:1:11:2 | FunctionExpr | test.py:9:25:9:26 | UnaryExpr |
+| test.py:4:1:11:2 | FunctionExpr | test.py:10:15:10:30 | Str |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.ql
new file mode 100644
index 00000000000..16acf1fda31
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.ql
@@ -0,0 +1,4 @@
+import python
+
+from FunctionExpr fe
+select fe, fe.getASubExpression()
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
new file mode 100644
index 00000000000..a8fdf1aba1b
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
@@ -0,0 +1,2 @@
+| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:5:21:5:22 | UnaryExpr |
+| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:7:19:7:20 | UnaryExpr |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql
new file mode 100644
index 00000000000..58a1cd57037
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql
@@ -0,0 +1,4 @@
+import python
+
+from FunctionExpr fe
+select fe, fe.getArgs(), fe.getArgs().getADefault()
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
new file mode 100644
index 00000000000..014fa6aba35
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
@@ -0,0 +1 @@
+| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:9:25:9:26 | UnaryExpr |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql
new file mode 100644
index 00000000000..cb7db62f7cc
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql
@@ -0,0 +1,4 @@
+import python
+
+from FunctionExpr fe
+select fe, fe.getArgs(), fe.getArgs().getAKwDefault()
diff --git a/python/ql/test/3/library-tests/functions/test.py b/python/ql/test/3/library-tests/functions/test.py
new file mode 100644
index 00000000000..3f6780afcca
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/test.py
@@ -0,0 +1,20 @@
+# not importing typing so we don't need to filter by location in Ql tests
+
+
+def func(
+ pos_only: int = -1,
+ /,
+ normal: int = -2,
+ *args: "Tuple[str]",
+ keyword_only: int = -3,
+ **kwargs: "Dict[str, str]",
+):
+ print(pos_only, normal, keyword_only)
+ print(args)
+ print(kwargs)
+
+
+func(1, 2, keyword_only=3)
+func(4, normal=5, keyword_only=6)
+
+func(1, 2, "varargs0", "varargs1", keyword_only=3, kwargs0="0", kwargs1="1")
diff --git a/python/ql/test/3/library-tests/parameters/Annotations.expected b/python/ql/test/3/library-tests/parameters/Annotations.expected
new file mode 100644
index 00000000000..16be5c5354d
--- /dev/null
+++ b/python/ql/test/3/library-tests/parameters/Annotations.expected
@@ -0,0 +1,4 @@
+| args | test.py:8:12:8:23 | Str |
+| kwargs | test.py:10:15:10:30 | Str |
+| normal | test.py:7:13:7:15 | int |
+| pos_only | test.py:5:15:5:17 | int |
diff --git a/python/ql/test/3/library-tests/parameters/Annotations.ql b/python/ql/test/3/library-tests/parameters/Annotations.ql
new file mode 100644
index 00000000000..17b02844a44
--- /dev/null
+++ b/python/ql/test/3/library-tests/parameters/Annotations.ql
@@ -0,0 +1,4 @@
+import python
+
+from Parameter p
+select p.getName(), p.getAnnotation()
diff --git a/python/ql/test/3/library-tests/parameters/Defaults.expected b/python/ql/test/3/library-tests/parameters/Defaults.expected
new file mode 100644
index 00000000000..a88a9e7fbb7
--- /dev/null
+++ b/python/ql/test/3/library-tests/parameters/Defaults.expected
@@ -0,0 +1,2 @@
+| normal | test.py:7:19:7:20 | UnaryExpr |
+| pos_only | test.py:5:21:5:22 | UnaryExpr |
diff --git a/python/ql/test/3/library-tests/parameters/Defaults.ql b/python/ql/test/3/library-tests/parameters/Defaults.ql
new file mode 100644
index 00000000000..ebc8215074b
--- /dev/null
+++ b/python/ql/test/3/library-tests/parameters/Defaults.ql
@@ -0,0 +1,4 @@
+import python
+
+from Parameter p
+select p.getName(), p.getDefault()
diff --git a/python/ql/test/3/library-tests/parameters/Special.expected b/python/ql/test/3/library-tests/parameters/Special.expected
new file mode 100644
index 00000000000..5e65d342906
--- /dev/null
+++ b/python/ql/test/3/library-tests/parameters/Special.expected
@@ -0,0 +1,4 @@
+| args | varargs |
+| kwargs | kwargs |
+| normal | normal |
+| pos_only | normal |
diff --git a/python/ql/test/3/library-tests/parameters/Special.ql b/python/ql/test/3/library-tests/parameters/Special.ql
new file mode 100644
index 00000000000..4987599bc72
--- /dev/null
+++ b/python/ql/test/3/library-tests/parameters/Special.ql
@@ -0,0 +1,10 @@
+import python
+
+from Parameter p, string type
+where
+ p.isKwargs() and type = "kwargs"
+ or
+ p.isVarargs() and type = "varargs"
+ or
+ not p.isKwargs() and not p.isVarargs() and type = "normal"
+select p.getName(), type
diff --git a/python/ql/test/3/library-tests/parameters/test.py b/python/ql/test/3/library-tests/parameters/test.py
new file mode 100644
index 00000000000..3f6780afcca
--- /dev/null
+++ b/python/ql/test/3/library-tests/parameters/test.py
@@ -0,0 +1,20 @@
+# not importing typing so we don't need to filter by location in Ql tests
+
+
+def func(
+ pos_only: int = -1,
+ /,
+ normal: int = -2,
+ *args: "Tuple[str]",
+ keyword_only: int = -3,
+ **kwargs: "Dict[str, str]",
+):
+ print(pos_only, normal, keyword_only)
+ print(args)
+ print(kwargs)
+
+
+func(1, 2, keyword_only=3)
+func(4, normal=5, keyword_only=6)
+
+func(1, 2, "varargs0", "varargs1", keyword_only=3, kwargs0="0", kwargs1="1")
From 4185edc087aca3dbf460706f8b488b2bdfa4a759 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 27 Apr 2020 11:24:59 +0200
Subject: [PATCH 006/734] Python: Expand parameters/functions test
I want to ensure we handle when only _some_ parameters have default/annotations
---
.../src/semmle/python/objects/ObjectAPI.qll | 1 +
.../functions/Function.getAChildNode.expected | 5 ++++
.../functions/Function.getArg.expected | 3 +++
.../functions/Function.getArgByName.expected | 3 +++
.../FunctionExpr.getASubExpression.expected | 4 ++++
.../FunctionExpr.getArgs.getADefault.expected | 2 ++
...unctionExpr.getArgs.getAKwDefault.expected | 1 +
.../ql/test/3/library-tests/functions/test.py | 23 +++++++++++++++++++
.../parameters/Annotations.expected | 1 +
.../parameters/Defaults.expected | 2 ++
.../library-tests/parameters/Special.expected | 3 +++
.../test/3/library-tests/parameters/test.py | 23 +++++++++++++++++++
12 files changed, 71 insertions(+)
diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll
index 65b3326e602..4c115c50bdf 100644
--- a/python/ql/src/semmle/python/objects/ObjectAPI.qll
+++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll
@@ -383,6 +383,7 @@ class CallableValue extends Value {
exists(int n |
call.getArg(n) = result and
this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name
+ // TODO: and not positional only argument (Python 3.8+)
)
or
call.getArgByName(name) = result and
diff --git a/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected b/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
index 20aec45af67..87e1a656612 100644
--- a/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
+++ b/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
@@ -5,3 +5,8 @@
| test.py:4:1:11:2 | Function func | test.py:12:5:12:41 | ExprStmt |
| test.py:4:1:11:2 | Function func | test.py:13:5:13:15 | ExprStmt |
| test.py:4:1:11:2 | Function func | test.py:14:5:14:17 | ExprStmt |
+| test.py:23:1:31:2 | Function func2 | test.py:24:5:24:11 | pos_req |
+| test.py:23:1:31:2 | Function func2 | test.py:25:5:25:17 | pos_w_default |
+| test.py:23:1:31:2 | Function func2 | test.py:26:5:26:18 | pos_w_default2 |
+| test.py:23:1:31:2 | Function func2 | test.py:32:5:32:18 | ExprStmt |
+| test.py:23:1:31:2 | Function func2 | test.py:33:5:40:5 | ExprStmt |
diff --git a/python/ql/test/3/library-tests/functions/Function.getArg.expected b/python/ql/test/3/library-tests/functions/Function.getArg.expected
index 54575caa9ee..0469a6db200 100644
--- a/python/ql/test/3/library-tests/functions/Function.getArg.expected
+++ b/python/ql/test/3/library-tests/functions/Function.getArg.expected
@@ -1,2 +1,5 @@
| test.py:4:1:11:2 | Function func | 0 | test.py:5:5:5:12 | Parameter |
| test.py:4:1:11:2 | Function func | 1 | test.py:7:5:7:10 | Parameter |
+| test.py:23:1:31:2 | Function func2 | 0 | test.py:24:5:24:11 | Parameter |
+| test.py:23:1:31:2 | Function func2 | 1 | test.py:25:5:25:17 | Parameter |
+| test.py:23:1:31:2 | Function func2 | 2 | test.py:26:5:26:18 | Parameter |
diff --git a/python/ql/test/3/library-tests/functions/Function.getArgByName.expected b/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
index d9bad185fd6..8b4a6277425 100644
--- a/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
+++ b/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
@@ -1,2 +1,5 @@
| test.py:4:1:11:2 | Function func | normal | test.py:7:5:7:10 | Parameter |
| test.py:4:1:11:2 | Function func | pos_only | test.py:5:5:5:12 | Parameter |
+| test.py:23:1:31:2 | Function func2 | pos_req | test.py:24:5:24:11 | Parameter |
+| test.py:23:1:31:2 | Function func2 | pos_w_default | test.py:25:5:25:17 | Parameter |
+| test.py:23:1:31:2 | Function func2 | pos_w_default2 | test.py:26:5:26:18 | Parameter |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
index 76e5b5ab802..7d3f2b386ed 100644
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
@@ -5,3 +5,7 @@
| test.py:4:1:11:2 | FunctionExpr | test.py:8:12:8:23 | Str |
| test.py:4:1:11:2 | FunctionExpr | test.py:9:25:9:26 | UnaryExpr |
| test.py:4:1:11:2 | FunctionExpr | test.py:10:15:10:30 | Str |
+| test.py:23:1:31:2 | FunctionExpr | test.py:25:20:25:24 | Str |
+| test.py:23:1:31:2 | FunctionExpr | test.py:25:28:25:31 | None |
+| test.py:23:1:31:2 | FunctionExpr | test.py:26:20:26:23 | None |
+| test.py:23:1:31:2 | FunctionExpr | test.py:29:32:29:35 | None |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
index a8fdf1aba1b..84334e7ede9 100644
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
@@ -1,2 +1,4 @@
| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:5:21:5:22 | UnaryExpr |
| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:7:19:7:20 | UnaryExpr |
+| test.py:23:1:31:2 | FunctionExpr | Arguments | test.py:25:28:25:31 | None |
+| test.py:23:1:31:2 | FunctionExpr | Arguments | test.py:26:20:26:23 | None |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
index 014fa6aba35..15c914be3c5 100644
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
@@ -1 +1,2 @@
| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:9:25:9:26 | UnaryExpr |
+| test.py:23:1:31:2 | FunctionExpr | Arguments | test.py:29:32:29:35 | None |
diff --git a/python/ql/test/3/library-tests/functions/test.py b/python/ql/test/3/library-tests/functions/test.py
index 3f6780afcca..a81714491c0 100644
--- a/python/ql/test/3/library-tests/functions/test.py
+++ b/python/ql/test/3/library-tests/functions/test.py
@@ -18,3 +18,26 @@ func(1, 2, keyword_only=3)
func(4, normal=5, keyword_only=6)
func(1, 2, "varargs0", "varargs1", keyword_only=3, kwargs0="0", kwargs1="1")
+
+
+def func2(
+ pos_req,
+ pos_w_default: "foo" = None,
+ pos_w_default2=None,
+ *,
+ keyword_req,
+ keyword_w_default: "foo" = None,
+ keyword_also_req,
+):
+ print("func2")
+ print(
+ pos_req,
+ pos_w_default,
+ pos_w_default2,
+ keyword_req,
+ keyword_w_default,
+ keyword_also_req,
+ )
+
+
+func2(1, keyword_req=2, keyword_also_req=3)
diff --git a/python/ql/test/3/library-tests/parameters/Annotations.expected b/python/ql/test/3/library-tests/parameters/Annotations.expected
index 16be5c5354d..2464d89a718 100644
--- a/python/ql/test/3/library-tests/parameters/Annotations.expected
+++ b/python/ql/test/3/library-tests/parameters/Annotations.expected
@@ -2,3 +2,4 @@
| kwargs | test.py:10:15:10:30 | Str |
| normal | test.py:7:13:7:15 | int |
| pos_only | test.py:5:15:5:17 | int |
+| pos_w_default | test.py:25:20:25:24 | Str |
diff --git a/python/ql/test/3/library-tests/parameters/Defaults.expected b/python/ql/test/3/library-tests/parameters/Defaults.expected
index a88a9e7fbb7..d88fad203ae 100644
--- a/python/ql/test/3/library-tests/parameters/Defaults.expected
+++ b/python/ql/test/3/library-tests/parameters/Defaults.expected
@@ -1,2 +1,4 @@
| normal | test.py:7:19:7:20 | UnaryExpr |
| pos_only | test.py:5:21:5:22 | UnaryExpr |
+| pos_w_default | test.py:25:28:25:31 | None |
+| pos_w_default2 | test.py:26:20:26:23 | None |
diff --git a/python/ql/test/3/library-tests/parameters/Special.expected b/python/ql/test/3/library-tests/parameters/Special.expected
index 5e65d342906..557fd1eec3c 100644
--- a/python/ql/test/3/library-tests/parameters/Special.expected
+++ b/python/ql/test/3/library-tests/parameters/Special.expected
@@ -2,3 +2,6 @@
| kwargs | kwargs |
| normal | normal |
| pos_only | normal |
+| pos_req | normal |
+| pos_w_default | normal |
+| pos_w_default2 | normal |
diff --git a/python/ql/test/3/library-tests/parameters/test.py b/python/ql/test/3/library-tests/parameters/test.py
index 3f6780afcca..a81714491c0 100644
--- a/python/ql/test/3/library-tests/parameters/test.py
+++ b/python/ql/test/3/library-tests/parameters/test.py
@@ -18,3 +18,26 @@ func(1, 2, keyword_only=3)
func(4, normal=5, keyword_only=6)
func(1, 2, "varargs0", "varargs1", keyword_only=3, kwargs0="0", kwargs1="1")
+
+
+def func2(
+ pos_req,
+ pos_w_default: "foo" = None,
+ pos_w_default2=None,
+ *,
+ keyword_req,
+ keyword_w_default: "foo" = None,
+ keyword_also_req,
+):
+ print("func2")
+ print(
+ pos_req,
+ pos_w_default,
+ pos_w_default2,
+ keyword_req,
+ keyword_w_default,
+ keyword_also_req,
+ )
+
+
+func2(1, keyword_req=2, keyword_also_req=3)
From c508e89a0059a0d3b352a3e7d944ab588bb8b78a Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 27 Apr 2020 14:57:14 +0200
Subject: [PATCH 007/734] Python: Handle keyword-only arguments properly
---
python/ql/src/semmle/python/Function.qll | 16 +++++++++++++---
.../functions/Function.getAChildNode.expected | 4 ++++
.../functions/Function.getArgByName.expected | 4 ++++
.../FunctionExpr.getASubExpression.expected | 2 ++
.../3/library-tests/parameters/Special.expected | 4 ++++
5 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll
index 9a45ce1ae74..4d3ec5c37e5 100644
--- a/python/ql/src/semmle/python/Function.qll
+++ b/python/ql/src/semmle/python/Function.qll
@@ -49,7 +49,11 @@ class Function extends Function_, Scope, AstNode {
string getArgName(int index) { result = this.getArg(index).(Name).getId() }
Parameter getArgByName(string name) {
- result = this.getAnArg() and
+ (
+ result = this.getAnArg()
+ or
+ result = this.getAKeywordOnlyArg()
+ ) and
result.(Name).getId() = name
}
@@ -102,6 +106,7 @@ class Function extends Function_, Scope, AstNode {
result = this.getAStmt() or
result = this.getAnArg() or
result = this.getVararg() or
+ result = this.getAKeywordOnlyArg() or
result = this.getKwarg()
}
@@ -185,6 +190,8 @@ class Parameter extends Parameter_ {
f.getVararg() = this
or
f.getKwarg() = this
+ or
+ f.getAKeywordOnlyArg() = this
)
}
@@ -322,11 +329,14 @@ class Lambda extends Lambda_, CallableExpr {
* that is generally only used for type hints today (PEP 484).
*/
class Arguments extends Arguments_ {
+
Expr getASubExpression() {
+ result = this.getADefault() or
result = this.getAKwDefault() or
+ //
result = this.getAnAnnotation() or
- result = this.getKwargannotation() or
result = this.getVarargannotation() or
- result = this.getADefault()
+ result = this.getAKwAnnotation() or
+ result = this.getKwargannotation()
}
}
diff --git a/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected b/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
index 87e1a656612..2f6fe749f4a 100644
--- a/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
+++ b/python/ql/test/3/library-tests/functions/Function.getAChildNode.expected
@@ -1,6 +1,7 @@
| test.py:4:1:11:2 | Function func | test.py:5:5:5:12 | pos_only |
| test.py:4:1:11:2 | Function func | test.py:7:5:7:10 | normal |
| test.py:4:1:11:2 | Function func | test.py:8:6:8:9 | args |
+| test.py:4:1:11:2 | Function func | test.py:9:5:9:16 | keyword_only |
| test.py:4:1:11:2 | Function func | test.py:10:7:10:12 | kwargs |
| test.py:4:1:11:2 | Function func | test.py:12:5:12:41 | ExprStmt |
| test.py:4:1:11:2 | Function func | test.py:13:5:13:15 | ExprStmt |
@@ -8,5 +9,8 @@
| test.py:23:1:31:2 | Function func2 | test.py:24:5:24:11 | pos_req |
| test.py:23:1:31:2 | Function func2 | test.py:25:5:25:17 | pos_w_default |
| test.py:23:1:31:2 | Function func2 | test.py:26:5:26:18 | pos_w_default2 |
+| test.py:23:1:31:2 | Function func2 | test.py:28:5:28:15 | keyword_req |
+| test.py:23:1:31:2 | Function func2 | test.py:29:5:29:21 | keyword_w_default |
+| test.py:23:1:31:2 | Function func2 | test.py:30:5:30:20 | keyword_also_req |
| test.py:23:1:31:2 | Function func2 | test.py:32:5:32:18 | ExprStmt |
| test.py:23:1:31:2 | Function func2 | test.py:33:5:40:5 | ExprStmt |
diff --git a/python/ql/test/3/library-tests/functions/Function.getArgByName.expected b/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
index 8b4a6277425..48682cdd5e0 100644
--- a/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
+++ b/python/ql/test/3/library-tests/functions/Function.getArgByName.expected
@@ -1,5 +1,9 @@
+| test.py:4:1:11:2 | Function func | keyword_only | test.py:9:5:9:16 | Parameter |
| test.py:4:1:11:2 | Function func | normal | test.py:7:5:7:10 | Parameter |
| test.py:4:1:11:2 | Function func | pos_only | test.py:5:5:5:12 | Parameter |
+| test.py:23:1:31:2 | Function func2 | keyword_also_req | test.py:30:5:30:20 | Parameter |
+| test.py:23:1:31:2 | Function func2 | keyword_req | test.py:28:5:28:15 | Parameter |
+| test.py:23:1:31:2 | Function func2 | keyword_w_default | test.py:29:5:29:21 | Parameter |
| test.py:23:1:31:2 | Function func2 | pos_req | test.py:24:5:24:11 | Parameter |
| test.py:23:1:31:2 | Function func2 | pos_w_default | test.py:25:5:25:17 | Parameter |
| test.py:23:1:31:2 | Function func2 | pos_w_default2 | test.py:26:5:26:18 | Parameter |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
index 7d3f2b386ed..a4ef726012a 100644
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getASubExpression.expected
@@ -3,9 +3,11 @@
| test.py:4:1:11:2 | FunctionExpr | test.py:7:13:7:15 | int |
| test.py:4:1:11:2 | FunctionExpr | test.py:7:19:7:20 | UnaryExpr |
| test.py:4:1:11:2 | FunctionExpr | test.py:8:12:8:23 | Str |
+| test.py:4:1:11:2 | FunctionExpr | test.py:9:19:9:21 | int |
| test.py:4:1:11:2 | FunctionExpr | test.py:9:25:9:26 | UnaryExpr |
| test.py:4:1:11:2 | FunctionExpr | test.py:10:15:10:30 | Str |
| test.py:23:1:31:2 | FunctionExpr | test.py:25:20:25:24 | Str |
| test.py:23:1:31:2 | FunctionExpr | test.py:25:28:25:31 | None |
| test.py:23:1:31:2 | FunctionExpr | test.py:26:20:26:23 | None |
+| test.py:23:1:31:2 | FunctionExpr | test.py:29:24:29:28 | Str |
| test.py:23:1:31:2 | FunctionExpr | test.py:29:32:29:35 | None |
diff --git a/python/ql/test/3/library-tests/parameters/Special.expected b/python/ql/test/3/library-tests/parameters/Special.expected
index 557fd1eec3c..2b81e393e5e 100644
--- a/python/ql/test/3/library-tests/parameters/Special.expected
+++ b/python/ql/test/3/library-tests/parameters/Special.expected
@@ -1,4 +1,8 @@
| args | varargs |
+| keyword_also_req | normal |
+| keyword_only | normal |
+| keyword_req | normal |
+| keyword_w_default | normal |
| kwargs | kwargs |
| normal | normal |
| pos_only | normal |
From 8c1cfe52f6cf53f9672225c34f9b317e9bf74eb8 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 27 Apr 2020 15:20:53 +0200
Subject: [PATCH 008/734] Python: Use `getAKeywordOnlyArg` instead of
`getAKwonlyarg`
The result is the same, but `getAKeywordOnlyArg` is the method used everywhere
else in the code.
---
python/ql/src/semmle/python/Function.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll
index 4d3ec5c37e5..dc5801b54af 100644
--- a/python/ql/src/semmle/python/Function.qll
+++ b/python/ql/src/semmle/python/Function.qll
@@ -94,7 +94,7 @@ class Function extends Function_, Scope, AstNode {
int getPositionalParameterCount() { result = count(this.getAnArg()) }
/** Gets the number of keyword-only parameters */
- int getKeywordOnlyParameterCount() { result = count(this.getAKwonlyarg()) }
+ int getKeywordOnlyParameterCount() { result = count(this.getAKeywordOnlyArg()) }
/** Whether this function accepts a variable number of arguments. That is, whether it has a starred (*arg) parameter. */
predicate hasVarArg() { exists(this.getVararg()) }
From 5f6058363f51ca8b56dd699c3e97883a6ab0e358 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 27 Apr 2020 15:21:48 +0200
Subject: [PATCH 009/734] Python: Improve QLdoc for Parameter.getPosition
---
python/ql/src/semmle/python/Function.qll | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll
index dc5801b54af..465ff298eb8 100644
--- a/python/ql/src/semmle/python/Function.qll
+++ b/python/ql/src/semmle/python/Function.qll
@@ -235,7 +235,10 @@ class Parameter extends Parameter_ {
Variable getVariable() { result.getAnAccess() = this.asName() }
- /** Gets the position of this parameter */
+ /**
+ * Gets the position of this parameter (if any).
+ * No result if this is a "varargs", "kwargs", or keyword-only parameter.
+ */
int getPosition() { exists(Function f | f.getArg(result) = this) }
/** Gets the name of this parameter */
From 1fcbb6e9f4312b8a705da328a8198e1442606ab7 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 27 Apr 2020 15:24:05 +0200
Subject: [PATCH 010/734] Python: Better test for Argument.getDefault(i)
Default values for positional arugments follow a rule, so if an argument has a
default value, later positional arguments must also have default values.
The database only stores the actual default values, and nothing about the
arguments that doesn't have default values.
This turns out to be a major problem for Argument.getKwDefault(i), since default
values for keyword-only arguments doesn't have the same rule. So if you know
there is one default value, you can't tell if it is associated with `foo` or
`bar`, as in the examples below:
```
def a(*, foo=None, bar):
pass
def b(*, foo, bar=None):
pass
```
---
.../functions/FunctionExpr.getArgs.getADefault.expected | 4 ----
.../functions/FunctionExpr.getArgs.getADefault.ql | 4 ----
.../functions/FunctionExpr.getArgs.getAKwDefault.expected | 2 --
.../functions/FunctionExpr.getArgs.getAKwDefault.ql | 4 ----
.../functions/FunctionExpr.getArgs.getAnnotation.expected | 3 +++
.../functions/FunctionExpr.getArgs.getAnnotation.ql | 4 ++++
.../functions/FunctionExpr.getArgs.getDefault.expected | 4 ++++
.../functions/FunctionExpr.getArgs.getDefault.ql | 4 ++++
.../functions/FunctionExpr.getArgs.getKwAnnotation.expected | 2 ++
.../functions/FunctionExpr.getArgs.getKwAnnotation.ql | 4 ++++
.../functions/FunctionExpr.getArgs.getKwDefault.expected | 2 ++
.../functions/FunctionExpr.getArgs.getKwDefault.ql | 4 ++++
12 files changed, 27 insertions(+), 14 deletions(-)
delete mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
delete mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql
delete mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
delete mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.expected
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.ql
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.ql
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.expected
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.ql
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected
create mode 100644 python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.ql
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
deleted file mode 100644
index 84334e7ede9..00000000000
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.expected
+++ /dev/null
@@ -1,4 +0,0 @@
-| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:5:21:5:22 | UnaryExpr |
-| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:7:19:7:20 | UnaryExpr |
-| test.py:23:1:31:2 | FunctionExpr | Arguments | test.py:25:28:25:31 | None |
-| test.py:23:1:31:2 | FunctionExpr | Arguments | test.py:26:20:26:23 | None |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql
deleted file mode 100644
index 58a1cd57037..00000000000
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getADefault.ql
+++ /dev/null
@@ -1,4 +0,0 @@
-import python
-
-from FunctionExpr fe
-select fe, fe.getArgs(), fe.getArgs().getADefault()
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
deleted file mode 100644
index 15c914be3c5..00000000000
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.expected
+++ /dev/null
@@ -1,2 +0,0 @@
-| test.py:4:1:11:2 | FunctionExpr | Arguments | test.py:9:25:9:26 | UnaryExpr |
-| test.py:23:1:31:2 | FunctionExpr | Arguments | test.py:29:32:29:35 | None |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql
deleted file mode 100644
index cb7db62f7cc..00000000000
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAKwDefault.ql
+++ /dev/null
@@ -1,4 +0,0 @@
-import python
-
-from FunctionExpr fe
-select fe, fe.getArgs(), fe.getArgs().getAKwDefault()
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.expected
new file mode 100644
index 00000000000..71573e3ad26
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.expected
@@ -0,0 +1,3 @@
+| test.py:4:1:11:2 | FunctionExpr | 0 | test.py:5:15:5:17 | int |
+| test.py:4:1:11:2 | FunctionExpr | 1 | test.py:7:13:7:15 | int |
+| test.py:23:1:31:2 | FunctionExpr | 1 | test.py:25:20:25:24 | Str |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.ql
new file mode 100644
index 00000000000..a1fdd4d443b
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getAnnotation.ql
@@ -0,0 +1,4 @@
+import python
+
+from FunctionExpr fe, int i
+select fe, i, fe.getArgs().getAnnotation(i)
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected
new file mode 100644
index 00000000000..a3634b57600
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected
@@ -0,0 +1,4 @@
+| test.py:4:1:11:2 | FunctionExpr | 0 | test.py:5:21:5:22 | UnaryExpr |
+| test.py:4:1:11:2 | FunctionExpr | 1 | test.py:7:19:7:20 | UnaryExpr |
+| test.py:23:1:31:2 | FunctionExpr | 0 | test.py:25:28:25:31 | None |
+| test.py:23:1:31:2 | FunctionExpr | 1 | test.py:26:20:26:23 | None |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.ql
new file mode 100644
index 00000000000..21ceeef5491
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.ql
@@ -0,0 +1,4 @@
+import python
+
+from FunctionExpr fe, int i
+select fe, i, fe.getArgs().getDefault(i)
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.expected
new file mode 100644
index 00000000000..c3e879fb722
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.expected
@@ -0,0 +1,2 @@
+| test.py:4:1:11:2 | FunctionExpr | 0 | test.py:9:19:9:21 | int |
+| test.py:23:1:31:2 | FunctionExpr | 1 | test.py:29:24:29:28 | Str |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.ql
new file mode 100644
index 00000000000..935dd68243c
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwAnnotation.ql
@@ -0,0 +1,4 @@
+import python
+
+from FunctionExpr fe, int i
+select fe, i, fe.getArgs().getKwAnnotation(i)
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected
new file mode 100644
index 00000000000..1303c58fa32
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected
@@ -0,0 +1,2 @@
+| test.py:4:1:11:2 | FunctionExpr | 0 | test.py:9:25:9:26 | UnaryExpr |
+| test.py:23:1:31:2 | FunctionExpr | 0 | test.py:29:32:29:35 | None |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.ql b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.ql
new file mode 100644
index 00000000000..0bf959b2179
--- /dev/null
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.ql
@@ -0,0 +1,4 @@
+import python
+
+from FunctionExpr fe, int i
+select fe, i, fe.getArgs().getKwDefault(i)
From c5e14f5c0d30f63a9e453e3bd04e9d1795099af8 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 27 Apr 2020 15:36:13 +0200
Subject: [PATCH 011/734] Python: Handle defaults and annotations for
keyword-only arguments
This commit is based on a change to the extractor
---
python/ql/src/semmle/python/AstGenerated.qll | 12 +++---
python/ql/src/semmle/python/Function.qll | 43 +++++++++++++++----
.../FunctionExpr.getArgs.getDefault.expected | 4 +-
...FunctionExpr.getArgs.getKwDefault.expected | 2 +-
.../parameters/Annotations.expected | 2 +
.../parameters/Defaults.expected | 2 +
6 files changed, 48 insertions(+), 17 deletions(-)
diff --git a/python/ql/src/semmle/python/AstGenerated.qll b/python/ql/src/semmle/python/AstGenerated.qll
index 13e803650cc..30294fb8395 100644
--- a/python/ql/src/semmle/python/AstGenerated.qll
+++ b/python/ql/src/semmle/python/AstGenerated.qll
@@ -1191,13 +1191,13 @@ library class AliasList_ extends @py_alias_list {
}
library class Arguments_ extends @py_arguments {
- /** Gets the keyword default values of this parameters definition. */
+ /** Gets the keyword-only default values of this parameters definition. */
ExprList getKwDefaults() { py_expr_lists(result, this, 0) }
- /** Gets the nth keyword default value of this parameters definition. */
+ /** Gets the nth keyword-only default value of this parameters definition. */
Expr getKwDefault(int index) { result = this.getKwDefaults().getItem(index) }
- /** Gets a keyword default value of this parameters definition. */
+ /** Gets a keyword-only default value of this parameters definition. */
Expr getAKwDefault() { result = this.getKwDefaults().getAnItem() }
/** Gets the default values of this parameters definition. */
@@ -1224,13 +1224,13 @@ library class Arguments_ extends @py_arguments {
/** Gets the **kwarg annotation of this parameters definition. */
Expr getKwargannotation() { py_exprs(result, _, this, 4) }
- /** Gets the kw_annotations of this parameters definition. */
+ /** Gets the keyword-only annotations of this parameters definition. */
ExprList getKwAnnotations() { py_expr_lists(result, this, 5) }
- /** Gets the nth kw_annotation of this parameters definition. */
+ /** Gets the nth keyword-only annotation of this parameters definition. */
Expr getKwAnnotation(int index) { result = this.getKwAnnotations().getItem(index) }
- /** Gets a kw_annotation of this parameters definition. */
+ /** Gets a keyword-only annotation of this parameters definition. */
Expr getAKwAnnotation() { result = this.getKwAnnotations().getAnItem() }
ArgumentsParent getParent() { py_arguments(this, result) }
diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll
index 465ff298eb8..b5cd9b4db4f 100644
--- a/python/ql/src/semmle/python/Function.qll
+++ b/python/ql/src/semmle/python/Function.qll
@@ -209,19 +209,31 @@ class Parameter extends Parameter_ {
/** Gets the expression for the default value of this parameter */
Expr getDefault() {
- exists(Function f, int n, int c, int d, Arguments args | args = f.getDefinition().getArgs() |
- f.getArg(n) = this and
- c = count(f.getAnArg()) and
- d = count(args.getADefault()) and
- result = args.getDefault(d - c + n)
+ exists(Function f, int i, Arguments args | args = f.getDefinition().getArgs() |
+ // positional (normal)
+ f.getArg(i) = this and
+ result = args.getDefault(i)
+ )
+ or
+ exists(Function f, int i, Arguments args | args = f.getDefinition().getArgs() |
+ // keyword-only
+ f.getKeywordOnlyArg(i) = this and
+ result = args.getKwDefault(i)
)
}
/** Gets the annotation expression of this parameter */
Expr getAnnotation() {
- exists(Function f, int n, Arguments args | args = f.getDefinition().getArgs() |
- f.getArg(n) = this and
- result = args.getAnnotation(n)
+ exists(Function f, int i, Arguments args | args = f.getDefinition().getArgs() |
+ // positional (normal)
+ f.getArg(i) = this and
+ result = args.getAnnotation(i)
+ )
+ or
+ exists(Function f, int i, Arguments args | args = f.getDefinition().getArgs() |
+ // keyword-only
+ f.getKeywordOnlyArg(i) = this and
+ result = args.getKwAnnotation(i)
)
or
exists(Function f, Arguments args | args = f.getDefinition().getArgs() |
@@ -342,4 +354,19 @@ class Arguments extends Arguments_ {
result = this.getAKwAnnotation() or
result = this.getKwargannotation()
}
+
+ // The following 4 methods are overwritten to provide better QLdoc. Since the
+ // Arguments_ is auto-generated, we can't change the poor auto-generated docs there :(
+
+ /** Gets the default value for the `index`'th positional parameter. */
+ override Expr getDefault(int index) { result = super.getDefault(index) }
+
+ /** Gets the default value for the `index`'th keyword-only parameter. */
+ override Expr getKwDefault(int index) { result = super.getKwDefault(index) }
+
+ /** Gets the annotation for the `index`'th positional parameter. */
+ override Expr getAnnotation(int index) { result = super.getAnnotation(index) }
+
+ /** Gets the annotation for the `index`'th keyword-only parameter. */
+ override Expr getKwAnnotation(int index) { result = super.getKwAnnotation(index) }
}
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected
index a3634b57600..5e50bddb200 100644
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getDefault.expected
@@ -1,4 +1,4 @@
| test.py:4:1:11:2 | FunctionExpr | 0 | test.py:5:21:5:22 | UnaryExpr |
| test.py:4:1:11:2 | FunctionExpr | 1 | test.py:7:19:7:20 | UnaryExpr |
-| test.py:23:1:31:2 | FunctionExpr | 0 | test.py:25:28:25:31 | None |
-| test.py:23:1:31:2 | FunctionExpr | 1 | test.py:26:20:26:23 | None |
+| test.py:23:1:31:2 | FunctionExpr | 1 | test.py:25:28:25:31 | None |
+| test.py:23:1:31:2 | FunctionExpr | 2 | test.py:26:20:26:23 | None |
diff --git a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected
index 1303c58fa32..c5ba91814e5 100644
--- a/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected
+++ b/python/ql/test/3/library-tests/functions/FunctionExpr.getArgs.getKwDefault.expected
@@ -1,2 +1,2 @@
| test.py:4:1:11:2 | FunctionExpr | 0 | test.py:9:25:9:26 | UnaryExpr |
-| test.py:23:1:31:2 | FunctionExpr | 0 | test.py:29:32:29:35 | None |
+| test.py:23:1:31:2 | FunctionExpr | 1 | test.py:29:32:29:35 | None |
diff --git a/python/ql/test/3/library-tests/parameters/Annotations.expected b/python/ql/test/3/library-tests/parameters/Annotations.expected
index 2464d89a718..91835eac419 100644
--- a/python/ql/test/3/library-tests/parameters/Annotations.expected
+++ b/python/ql/test/3/library-tests/parameters/Annotations.expected
@@ -1,4 +1,6 @@
| args | test.py:8:12:8:23 | Str |
+| keyword_only | test.py:9:19:9:21 | int |
+| keyword_w_default | test.py:29:24:29:28 | Str |
| kwargs | test.py:10:15:10:30 | Str |
| normal | test.py:7:13:7:15 | int |
| pos_only | test.py:5:15:5:17 | int |
diff --git a/python/ql/test/3/library-tests/parameters/Defaults.expected b/python/ql/test/3/library-tests/parameters/Defaults.expected
index d88fad203ae..84cd7967c80 100644
--- a/python/ql/test/3/library-tests/parameters/Defaults.expected
+++ b/python/ql/test/3/library-tests/parameters/Defaults.expected
@@ -1,3 +1,5 @@
+| keyword_only | test.py:9:25:9:26 | UnaryExpr |
+| keyword_w_default | test.py:29:32:29:35 | None |
| normal | test.py:7:19:7:20 | UnaryExpr |
| pos_only | test.py:5:21:5:22 | UnaryExpr |
| pos_w_default | test.py:25:28:25:31 | None |
From 31a2972ecaa4d8aaf35fa99c1c740de107ef512b Mon Sep 17 00:00:00 2001
From: Grzegorz Golawski
Date: Mon, 27 Apr 2020 23:32:48 +0200
Subject: [PATCH 012/734] Remove qlpack.yml as these are not needed
---
java/ql/src/experimental/qlpack.yml | 4 ----
java/ql/test/experimental/qlpack.yml | 4 ----
.../query-tests/security/CWE-917/OgnlInjection.qlref | 2 +-
3 files changed, 1 insertion(+), 9 deletions(-)
delete mode 100644 java/ql/src/experimental/qlpack.yml
delete mode 100644 java/ql/test/experimental/qlpack.yml
diff --git a/java/ql/src/experimental/qlpack.yml b/java/ql/src/experimental/qlpack.yml
deleted file mode 100644
index bf76fc1b736..00000000000
--- a/java/ql/src/experimental/qlpack.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-name: codeql-java-experimental
-version: 0.0.0
-libraryPathDependencies: codeql-java
-extractor: java
diff --git a/java/ql/test/experimental/qlpack.yml b/java/ql/test/experimental/qlpack.yml
deleted file mode 100644
index 4b7a7635a6c..00000000000
--- a/java/ql/test/experimental/qlpack.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-name: codeql-java-experimental-tests
-version: 0.0.0
-libraryPathDependencies: codeql-java-experimental
-extractor: java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref
index b72d532c765..668f3bf2797 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref
+++ b/java/ql/test/experimental/query-tests/security/CWE-917/OgnlInjection.qlref
@@ -1 +1 @@
-Security/CWE/CWE-917/OgnlInjection.ql
+experimental/Security/CWE/CWE-917/OgnlInjection.ql
From a15833d194f010982b90ee9ac675bd843ab3b3c1 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 6 May 2020 09:05:22 +0200
Subject: [PATCH 013/734] Python: DB upgrade script for default-indexing change
Follow this excellent guide:
https://github.com/github/codeql-c-extractor-team/blob/master/docs/db-upgrade.md
---
python/ql/src/semmlecode.python.dbscheme | 16 +-
.../old.dbscheme | 988 +++++++++++++++++
.../py_exprs.ql | 123 +++
.../semmlecode.python.dbscheme | 992 ++++++++++++++++++
.../upgrade.properties | 3 +
5 files changed, 2116 insertions(+), 6 deletions(-)
create mode 100644 python/upgrades/f635b392038a494915307f913657cd3058f9b476/old.dbscheme
create mode 100644 python/upgrades/f635b392038a494915307f913657cd3058f9b476/py_exprs.ql
create mode 100644 python/upgrades/f635b392038a494915307f913657cd3058f9b476/semmlecode.python.dbscheme
create mode 100644 python/upgrades/f635b392038a494915307f913657cd3058f9b476/upgrade.properties
diff --git a/python/ql/src/semmlecode.python.dbscheme b/python/ql/src/semmlecode.python.dbscheme
index f635b392038..6adf7d03ec2 100644
--- a/python/ql/src/semmlecode.python.dbscheme
+++ b/python/ql/src/semmlecode.python.dbscheme
@@ -5,6 +5,10 @@
* by adding rules to dbscheme.template
*/
+/* This is a dummy line to alter the dbscheme, so we can make a database upgrade
+ * 2020-05-04
+ */
+
/*
* External artifacts
*/
@@ -126,7 +130,7 @@ containerparent(int parent: @container ref,
unique int child: @container ref);
@sourceline = @file | @py_Module | @xmllocatable;
-
+
numlines(int element_id: @sourceline ref,
int num_lines: int ref,
int num_code: int ref,
@@ -922,7 +926,7 @@ ext_rettype(int funcid : @py_object ref,
ext_proptype(int propid : @py_object ref,
int typeid : @py_object ref);
-ext_argreturn(int funcid : @py_object ref,
+ext_argreturn(int funcid : @py_object ref,
int arg : int ref);
py_special_objects(unique int obj : @py_cobject ref,
@@ -935,7 +939,7 @@ py_decorated_object(int object : @py_object ref,
@py_source_element = @py_ast_node | @container;
-/* XML Files */
+/* XML Files */
xmlEncoding (unique int id: @file ref, varchar(900) encoding: string ref);
@@ -943,7 +947,7 @@ xmlDTDs (unique int id: @xmldtd,
varchar(900) root: string ref,
varchar(900) publicId: string ref,
varchar(900) systemId: string ref,
- int fileid: @file ref);
+ int fileid: @file ref);
xmlElements (unique int id: @xmlelement,
varchar(900) name: string ref,
@@ -958,7 +962,7 @@ xmlAttrs (unique int id: @xmlattribute,
int idx: int ref,
int fileid: @file ref);
-xmlNs (int id: @xmlnamespace,
+xmlNs (int id: @xmlnamespace,
varchar(900) prefixName: string ref,
varchar(900) URI: string ref,
int fileid: @file ref);
@@ -970,7 +974,7 @@ xmlHasNs (int elementId: @xmlnamespaceable ref,
xmlComments (unique int id: @xmlcomment,
varchar(3600) text: string ref,
int parentid: @xmlparent ref,
- int fileid: @file ref);
+ int fileid: @file ref);
xmlChars (unique int id: @xmlcharacters,
varchar(3600) text: string ref,
diff --git a/python/upgrades/f635b392038a494915307f913657cd3058f9b476/old.dbscheme b/python/upgrades/f635b392038a494915307f913657cd3058f9b476/old.dbscheme
new file mode 100644
index 00000000000..f635b392038
--- /dev/null
+++ b/python/upgrades/f635b392038a494915307f913657cd3058f9b476/old.dbscheme
@@ -0,0 +1,988 @@
+/*
+ * This dbscheme is auto-generated by 'semmle/dbscheme_gen.py'.
+ * WARNING: Any modifications to this file will be lost.
+ * Relations can be changed by modifying master.py or
+ * by adding rules to dbscheme.template
+ */
+
+ /*
+ * External artifacts
+ */
+
+externalDefects(
+ unique int id : @externalDefect,
+ varchar(900) queryPath : string ref,
+ int location : @location ref,
+ varchar(900) message : string ref,
+ float severity : float ref
+);
+
+externalMetrics(
+ unique int id : @externalMetric,
+ varchar(900) queryPath : string ref,
+ int location : @location ref,
+ float value : float ref
+);
+
+externalData(
+ int id : @externalDataElement,
+ varchar(900) queryPath : string ref,
+ int column: int ref,
+ varchar(900) data : string ref
+);
+
+snapshotDate(unique date snapshotDate : date ref);
+
+sourceLocationPrefix(varchar(900) prefix : string ref);
+
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ varchar(900) relativePath : string ref,
+ int equivClass : int ref);
+
+similarCode(
+ unique int id : @similarity,
+ varchar(900) 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);
+
+/*
+ * Line metrics
+ */
+py_codelines(int id : @py_scope ref,
+ int count : int ref);
+
+py_commentlines(int id : @py_scope ref,
+ int count : int ref);
+
+py_docstringlines(int id : @py_scope ref,
+ int count : int ref);
+
+py_alllines(int id : @py_scope ref,
+ int count : int ref);
+
+/*
+ * Version history
+ */
+
+svnentries(
+ int id : @svnentry,
+ varchar(500) revision : string ref,
+ varchar(500) author : string ref,
+ date revisionDate : date ref,
+ int changeSize : int ref
+)
+
+svnaffectedfiles(
+ int id : @svnentry ref,
+ int file : @file ref,
+ varchar(500) action : string ref
+)
+
+svnentrymsg(
+ int id : @svnentry ref,
+ varchar(500) message : string ref
+)
+
+svnchurn(
+ int commit : @svnentry ref,
+ int file : @file ref,
+ int addedLines : int ref,
+ int deletedLines : int ref
+)
+
+/****************************
+ Python dbscheme
+****************************/
+
+/* fromSource is ignored */
+files(unique int id: @file,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref,
+ varchar(900) ext: string ref,
+ int fromSource: int ref);
+
+folders(unique int id: @folder,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref);
+
+@container = @folder | @file;
+
+containerparent(int parent: @container ref,
+ unique int child: @container ref);
+
+@sourceline = @file | @py_Module | @xmllocatable;
+
+numlines(int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+ );
+
+@location = @location_ast | @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);
+
+locations_ast(unique int id: @location_ast,
+ int module: @py_Module ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref);
+
+file_contents(unique int file: @file ref, string contents: string ref);
+
+py_module_path(int module: @py_Module ref, int file: @container ref);
+
+variable(unique int id : @py_variable,
+ int scope : @py_scope ref,
+ varchar(1) name : string ref);
+
+py_line_lengths(unique int id : @py_line,
+ int file: @py_Module ref,
+ int line : int ref,
+ int length : int ref);
+
+py_extracted_version(int module : @py_Module ref,
+ varchar(1) version : string ref);
+
+/* AUTO GENERATED PART STARTS HERE */
+
+
+/* AnnAssign.location = 0, location */
+/* AnnAssign.value = 1, expr */
+/* AnnAssign.annotation = 2, expr */
+/* AnnAssign.target = 3, expr */
+
+/* Assert.location = 0, location */
+/* Assert.test = 1, expr */
+/* Assert.msg = 2, expr */
+
+/* Assign.location = 0, location */
+/* Assign.value = 1, expr */
+/* Assign.targets = 2, expr_list */
+
+/* AssignExpr.location = 0, location */
+/* AssignExpr.parenthesised = 1, bool */
+/* AssignExpr.value = 2, expr */
+/* AssignExpr.target = 3, expr */
+
+/* Attribute.location = 0, location */
+/* Attribute.parenthesised = 1, bool */
+/* Attribute.value = 2, expr */
+/* Attribute.attr = 3, str */
+/* Attribute.ctx = 4, expr_context */
+
+/* AugAssign.location = 0, location */
+/* AugAssign.operation = 1, BinOp */
+
+/* Await.location = 0, location */
+/* Await.parenthesised = 1, bool */
+/* Await.value = 2, expr */
+
+/* BinaryExpr.location = 0, location */
+/* BinaryExpr.parenthesised = 1, bool */
+/* BinaryExpr.left = 2, expr */
+/* BinaryExpr.op = 3, operator */
+/* BinaryExpr.right = 4, expr */
+/* BinaryExpr = AugAssign */
+
+/* BoolExpr.location = 0, location */
+/* BoolExpr.parenthesised = 1, bool */
+/* BoolExpr.op = 2, boolop */
+/* BoolExpr.values = 3, expr_list */
+
+/* Break.location = 0, location */
+
+/* Bytes.location = 0, location */
+/* Bytes.parenthesised = 1, bool */
+/* Bytes.s = 2, bytes */
+/* Bytes.prefix = 3, bytes */
+/* Bytes.implicitly_concatenated_parts = 4, StringPart_list */
+
+/* Call.location = 0, location */
+/* Call.parenthesised = 1, bool */
+/* Call.func = 2, expr */
+/* Call.positional_args = 3, expr_list */
+/* Call.named_args = 4, dict_item_list */
+
+/* Class.name = 0, str */
+/* Class.body = 1, stmt_list */
+/* Class = ClassExpr */
+
+/* ClassExpr.location = 0, location */
+/* ClassExpr.parenthesised = 1, bool */
+/* ClassExpr.name = 2, str */
+/* ClassExpr.bases = 3, expr_list */
+/* ClassExpr.keywords = 4, dict_item_list */
+/* ClassExpr.inner_scope = 5, Class */
+
+/* Compare.location = 0, location */
+/* Compare.parenthesised = 1, bool */
+/* Compare.left = 2, expr */
+/* Compare.ops = 3, cmpop_list */
+/* Compare.comparators = 4, expr_list */
+
+/* Continue.location = 0, location */
+
+/* Delete.location = 0, location */
+/* Delete.targets = 1, expr_list */
+
+/* Dict.location = 0, location */
+/* Dict.parenthesised = 1, bool */
+/* Dict.items = 2, dict_item_list */
+
+/* DictComp.location = 0, location */
+/* DictComp.parenthesised = 1, bool */
+/* DictComp.function = 2, Function */
+/* DictComp.iterable = 3, expr */
+
+/* DictUnpacking.location = 0, location */
+/* DictUnpacking.value = 1, expr */
+
+/* Ellipsis.location = 0, location */
+/* Ellipsis.parenthesised = 1, bool */
+
+/* ExceptStmt.location = 0, location */
+/* ExceptStmt.type = 1, expr */
+/* ExceptStmt.name = 2, expr */
+/* ExceptStmt.body = 3, stmt_list */
+
+/* Exec.location = 0, location */
+/* Exec.body = 1, expr */
+/* Exec.globals = 2, expr */
+/* Exec.locals = 3, expr */
+
+/* ExprStmt.location = 0, location */
+/* ExprStmt.value = 1, expr */
+
+/* Filter.location = 0, location */
+/* Filter.parenthesised = 1, bool */
+/* Filter.value = 2, expr */
+/* Filter.filter = 3, expr */
+
+/* For.location = 0, location */
+/* For.target = 1, expr */
+/* For.iter = 2, expr */
+/* For.body = 3, stmt_list */
+/* For.orelse = 4, stmt_list */
+/* For.is_async = 5, bool */
+
+/* FormattedValue.location = 0, location */
+/* FormattedValue.parenthesised = 1, bool */
+/* FormattedValue.value = 2, expr */
+/* FormattedValue.conversion = 3, str */
+/* FormattedValue.format_spec = 4, JoinedStr */
+
+/* Function.name = 0, str */
+/* Function.args = 1, parameter_list */
+/* Function.vararg = 2, expr */
+/* Function.kwonlyargs = 3, expr_list */
+/* Function.kwarg = 4, expr */
+/* Function.body = 5, stmt_list */
+/* Function.is_async = 6, bool */
+/* Function = FunctionParent */
+
+/* FunctionExpr.location = 0, location */
+/* FunctionExpr.parenthesised = 1, bool */
+/* FunctionExpr.name = 2, str */
+/* FunctionExpr.args = 3, arguments */
+/* FunctionExpr.returns = 4, expr */
+/* FunctionExpr.inner_scope = 5, Function */
+
+/* GeneratorExp.location = 0, location */
+/* GeneratorExp.parenthesised = 1, bool */
+/* GeneratorExp.function = 2, Function */
+/* GeneratorExp.iterable = 3, expr */
+
+/* Global.location = 0, location */
+/* Global.names = 1, str_list */
+
+/* If.location = 0, location */
+/* If.test = 1, expr */
+/* If.body = 2, stmt_list */
+/* If.orelse = 3, stmt_list */
+
+/* IfExp.location = 0, location */
+/* IfExp.parenthesised = 1, bool */
+/* IfExp.test = 2, expr */
+/* IfExp.body = 3, expr */
+/* IfExp.orelse = 4, expr */
+
+/* Import.location = 0, location */
+/* Import.names = 1, alias_list */
+
+/* ImportExpr.location = 0, location */
+/* ImportExpr.parenthesised = 1, bool */
+/* ImportExpr.level = 2, int */
+/* ImportExpr.name = 3, str */
+/* ImportExpr.top = 4, bool */
+
+/* ImportStar.location = 0, location */
+/* ImportStar.module = 1, expr */
+
+/* ImportMember.location = 0, location */
+/* ImportMember.parenthesised = 1, bool */
+/* ImportMember.module = 2, expr */
+/* ImportMember.name = 3, str */
+
+/* Fstring.location = 0, location */
+/* Fstring.parenthesised = 1, bool */
+/* Fstring.values = 2, expr_list */
+/* Fstring = FormattedValue */
+
+/* KeyValuePair.location = 0, location */
+/* KeyValuePair.value = 1, expr */
+/* KeyValuePair.key = 2, expr */
+
+/* Lambda.location = 0, location */
+/* Lambda.parenthesised = 1, bool */
+/* Lambda.args = 2, arguments */
+/* Lambda.inner_scope = 3, Function */
+
+/* List.location = 0, location */
+/* List.parenthesised = 1, bool */
+/* List.elts = 2, expr_list */
+/* List.ctx = 3, expr_context */
+
+/* ListComp.location = 0, location */
+/* ListComp.parenthesised = 1, bool */
+/* ListComp.function = 2, Function */
+/* ListComp.iterable = 3, expr */
+/* ListComp.generators = 4, comprehension_list */
+/* ListComp.elt = 5, expr */
+
+/* Module.name = 0, str */
+/* Module.hash = 1, str */
+/* Module.body = 2, stmt_list */
+/* Module.kind = 3, str */
+
+/* Name.location = 0, location */
+/* Name.parenthesised = 1, bool */
+/* Name.variable = 2, variable */
+/* Name.ctx = 3, expr_context */
+/* Name = ParameterList */
+
+/* Nonlocal.location = 0, location */
+/* Nonlocal.names = 1, str_list */
+
+/* Num.location = 0, location */
+/* Num.parenthesised = 1, bool */
+/* Num.n = 2, number */
+/* Num.text = 3, number */
+
+/* Pass.location = 0, location */
+
+/* PlaceHolder.location = 0, location */
+/* PlaceHolder.parenthesised = 1, bool */
+/* PlaceHolder.variable = 2, variable */
+/* PlaceHolder.ctx = 3, expr_context */
+
+/* Print.location = 0, location */
+/* Print.dest = 1, expr */
+/* Print.values = 2, expr_list */
+/* Print.nl = 3, bool */
+
+/* Raise.location = 0, location */
+/* Raise.exc = 1, expr */
+/* Raise.cause = 2, expr */
+/* Raise.type = 3, expr */
+/* Raise.inst = 4, expr */
+/* Raise.tback = 5, expr */
+
+/* Repr.location = 0, location */
+/* Repr.parenthesised = 1, bool */
+/* Repr.value = 2, expr */
+
+/* Return.location = 0, location */
+/* Return.value = 1, expr */
+
+/* Set.location = 0, location */
+/* Set.parenthesised = 1, bool */
+/* Set.elts = 2, expr_list */
+
+/* SetComp.location = 0, location */
+/* SetComp.parenthesised = 1, bool */
+/* SetComp.function = 2, Function */
+/* SetComp.iterable = 3, expr */
+
+/* Slice.location = 0, location */
+/* Slice.parenthesised = 1, bool */
+/* Slice.start = 2, expr */
+/* Slice.stop = 3, expr */
+/* Slice.step = 4, expr */
+
+/* SpecialOperation.location = 0, location */
+/* SpecialOperation.parenthesised = 1, bool */
+/* SpecialOperation.name = 2, str */
+/* SpecialOperation.arguments = 3, expr_list */
+
+/* Starred.location = 0, location */
+/* Starred.parenthesised = 1, bool */
+/* Starred.value = 2, expr */
+/* Starred.ctx = 3, expr_context */
+
+/* Str.location = 0, location */
+/* Str.parenthesised = 1, bool */
+/* Str.s = 2, str */
+/* Str.prefix = 3, str */
+/* Str.implicitly_concatenated_parts = 4, StringPart_list */
+
+/* StringPart.text = 0, str */
+/* StringPart.location = 1, location */
+/* StringPart = StringPartList */
+/* StringPartList = BytesOrStr */
+
+/* Subscript.location = 0, location */
+/* Subscript.parenthesised = 1, bool */
+/* Subscript.value = 2, expr */
+/* Subscript.index = 3, expr */
+/* Subscript.ctx = 4, expr_context */
+
+/* TemplateDottedNotation.location = 0, location */
+/* TemplateDottedNotation.parenthesised = 1, bool */
+/* TemplateDottedNotation.value = 2, expr */
+/* TemplateDottedNotation.attr = 3, str */
+/* TemplateDottedNotation.ctx = 4, expr_context */
+
+/* TemplateWrite.location = 0, location */
+/* TemplateWrite.value = 1, expr */
+
+/* Try.location = 0, location */
+/* Try.body = 1, stmt_list */
+/* Try.orelse = 2, stmt_list */
+/* Try.handlers = 3, stmt_list */
+/* Try.finalbody = 4, stmt_list */
+
+/*