From 2104cf55e3e50f2893668afdb78792a394fcb440 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 30 Aug 2018 10:18:49 +0200 Subject: [PATCH 01/50] JS: add models of URL requests --- javascript/ql/src/javascript.qll | 1 + .../javascript/frameworks/UrlRequests.qll | 127 ++++++++++++++++++ .../UrlRequests/UrlRequest.expected | 16 +++ .../frameworks/UrlRequests/UrlRequest.ql | 4 + .../frameworks/UrlRequests/tst.js | 39 ++++++ 5 files changed, 187 insertions(+) create mode 100644 javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll create mode 100644 javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.expected create mode 100644 javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql create mode 100644 javascript/ql/test/library-tests/frameworks/UrlRequests/tst.js diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index 436520d3fcf1..de99ecb0678b 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -71,6 +71,7 @@ import semmle.javascript.frameworks.Request import semmle.javascript.frameworks.SQL import semmle.javascript.frameworks.StringFormatters import semmle.javascript.frameworks.UriLibraries +import semmle.javascript.frameworks.UrlRequests import semmle.javascript.frameworks.XmlParsers import semmle.javascript.frameworks.xUnit import semmle.javascript.linters.ESLint diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll new file mode 100644 index 000000000000..e23d19fb287f --- /dev/null +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -0,0 +1,127 @@ +/** + * Provides classes for modelling URL requests. + * + * Subclass `UrlRequest` to refine the behavior of the analysis on existing URL requests. + * Subclass `CustomUrlRequest` to introduce new kinds of URL requests. + */ + +import javascript + +/** + * A call that performs a request to a URL. + */ +class CustomUrlRequest extends DataFlow::CallNode { + + /** + * Gets the URL of the request. + */ + abstract DataFlow::Node getUrl(); +} + +/** + * A call that performs a request to a URL. + */ +class UrlRequest extends DataFlow::CallNode { + + CustomUrlRequest custom; + + UrlRequest() { + this = custom + } + + /** + * Gets the URL of the request. + */ + DataFlow::Node getUrl() { + result = custom.getUrl() + } +} + +/** + * A simple model of common URL request libraries. + */ +private class DefaultUrlRequest extends CustomUrlRequest { + + DataFlow::Node url; + + DefaultUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee, string httpMethodName, string urlName | + httpMethodName = any(HTTP::RequestMethodName m).toLowerCase() and + (urlName = "url" or urlName = "uri") and // slightly over-approximate, in the name of simplicity + this = callee.getACall() | + ( + ( + moduleName = "request" or + moduleName = "request-promise" or + moduleName = "request-promise-any" or + moduleName = "request-promise-native" + ) and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, httpMethodName) + ) and + ( + url = getArgument(0) or + url = getOptionArgument(0, urlName) + ) + ) + or + ( + moduleName = "superagent" and + callee = DataFlow::moduleMember(moduleName, httpMethodName) and + url = getArgument(0) + ) + or + ( + (moduleName = "http" or moduleName = "https") and + callee = DataFlow::moduleMember(moduleName, httpMethodName) and + url = getArgument(0) + ) + or + ( + moduleName = "axios" and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, httpMethodName) or + callee = DataFlow::moduleMember(moduleName, "request") + ) and + ( + url = getArgument(0) or + url = getOptionArgument([0..2], urlName) // slightly over-approximate, in the name of simplicity + ) + ) + or + ( + moduleName = "got" and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, "stream") + ) and + ( + url = getArgument(0) and not exists (getOptionArgument(1, "baseUrl")) + ) + ) + or + ( + ( + moduleName = "node-fetch" or + moduleName = "cross-fetch" or + moduleName = "isomorphic-fetch" + ) and + callee = DataFlow::moduleImport(moduleName) and + url = getArgument(0) + ) + ) + or + ( + this = DataFlow::globalVarRef("fetch").getACall() and + url = getArgument(0) + ) + + } + + override DataFlow::Node getUrl() { + result = url + } + +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.expected b/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.expected new file mode 100644 index 000000000000..2b14dea32748 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.expected @@ -0,0 +1,16 @@ +| tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url | +| tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url | +| tst.js:15:5:15:23 | request.delete(url) | tst.js:15:20:15:22 | url | +| tst.js:17:5:17:25 | request ... url }) | tst.js:17:13:17:24 | { url: url } | +| tst.js:17:5:17:25 | request ... url }) | tst.js:17:20:17:22 | url | +| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:20:19:22 | url | +| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:20:21:22 | url | +| tst.js:23:5:23:17 | http.get(url) | tst.js:23:14:23:16 | url | +| tst.js:25:5:25:14 | axios(url) | tst.js:25:11:25:13 | url | +| tst.js:27:5:27:18 | axios.get(url) | tst.js:27:15:27:17 | url | +| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:11:29:22 | { url: url } | +| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:18:29:20 | url | +| tst.js:31:5:31:12 | got(url) | tst.js:31:9:31:11 | url | +| tst.js:33:5:33:19 | got.stream(url) | tst.js:33:16:33:18 | url | +| tst.js:35:5:35:21 | window.fetch(url) | tst.js:35:18:35:20 | url | +| tst.js:37:5:37:18 | nodeFetch(url) | tst.js:37:15:37:17 | url | diff --git a/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql b/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql new file mode 100644 index 000000000000..a1a4de127136 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql @@ -0,0 +1,4 @@ +import javascript + +from UrlRequest r +select r, r.getUrl() \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/UrlRequests/tst.js b/javascript/ql/test/library-tests/frameworks/UrlRequests/tst.js new file mode 100644 index 000000000000..3656e30d9660 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/UrlRequests/tst.js @@ -0,0 +1,39 @@ +import request from 'request'; +import requestPromise from 'request-promise'; +import superagent from 'superagent'; +import http from 'http'; +import express from 'express'; +import axios from 'axios'; +import got from 'got'; +import nodeFetch from 'node-fetch'; + +(function() { + request(url); + + request.get(url); + + request.delete(url); + + request({ url: url }); + + requestPromise(url); + + superagent.get(url); + + http.get(url); + + axios(url); + + axios.get(url); + + axios({ url: url }); + + got(url); + + got.stream(url); + + window.fetch(url); + + nodeFetch(url); + +}); From f5a6af54e67f277aa56e055fb3155e47c7b4a733 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 30 Aug 2018 10:19:18 +0200 Subject: [PATCH 02/50] JS: add security query: js/request-forgery --- javascript/config/suites/javascript/security | 1 + .../src/Security/CWE-918/RequestForgery.qhelp | 79 +++++++++++++++++ .../ql/src/Security/CWE-918/RequestForgery.ql | 18 ++++ .../CWE-918/examples/RequestForgeryBad.js | 12 +++ .../CWE-918/examples/RequestForgeryGood.js | 19 ++++ .../security/dataflow/RequestForgery.qll | 87 +++++++++++++++++++ .../Security/CWE-918/RequestForgery.expected | 6 ++ .../Security/CWE-918/RequestForgery.qlref | 1 + .../test/query-tests/Security/CWE-918/tst.js | 31 +++++++ 9 files changed, 254 insertions(+) create mode 100644 javascript/ql/src/Security/CWE-918/RequestForgery.qhelp create mode 100644 javascript/ql/src/Security/CWE-918/RequestForgery.ql create mode 100644 javascript/ql/src/Security/CWE-918/examples/RequestForgeryBad.js create mode 100644 javascript/ql/src/Security/CWE-918/examples/RequestForgeryGood.js create mode 100644 javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll create mode 100644 javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected create mode 100644 javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.qlref create mode 100644 javascript/ql/test/query-tests/Security/CWE-918/tst.js diff --git a/javascript/config/suites/javascript/security b/javascript/config/suites/javascript/security index 48878be87ce1..26debb63a24e 100644 --- a/javascript/config/suites/javascript/security +++ b/javascript/config/suites/javascript/security @@ -29,3 +29,4 @@ + semmlecode-javascript-queries/Security/CWE-807/DifferentKindsComparisonBypass.ql: /Security/CWE/CWE-807 + semmlecode-javascript-queries/Security/CWE-843/TypeConfusionThroughParameterTampering.ql: /Security/CWE/CWE-834 + semmlecode-javascript-queries/Security/CWE-916/InsufficientPasswordHash.ql: /Security/CWE/CWE-916 ++ semmlecode-javascript-queries/Security/CWE-918/RequestForgery.ql: /Security/CWE/CWE-918 diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp new file mode 100644 index 000000000000..75f0b681d168 --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp @@ -0,0 +1,79 @@ + + + + +

+ + Directly incorporating user input into a remote request + without validating the input can facilitate different kinds of request + forgery attacks, where the attacker essentially controls the request. + + If the vulnerable request is in server-side code, then security + mechanisms, such as external firewalls, can be bypassed. + + If the vulnerable request is in client-side code, then unsuspecting + users can send malicious requests to other servers, potentially + resulting in a DDOS attack. + +

+
+ + + +

+ + To guard against request forgery, it is advisable to avoid + putting user input directly into a remote request. If a flexible + remote request mechanism is required, it is recommended to maintain a + list of authorized request targets and choose from that list based on + the user input provided. + +

+ +
+ + + +

+ + The following example shows an HTTP request parameter + being used directly in a URL request without validating the input, + which facilitate an SSRF attack. The request + http.get(...) is vulnerable since an attacker can choose + the value of target to be anything he wants. For + instance, the attacker can choose + "internal.example.com/#" as the target, causing the URL + used in the request to be + "https://internal.example.com/#.example.com/data". + +

+ +

+ + A request to https://internal.example.com may + be problematic if that server is not meant to be + directly accessible from the attacker's machine. + +

+ + + +

+ + One way to remedy the problem is to use the user input to + select a known fixed string before performing the request: + +

+ + + +
+ + + +
  • OWASP: SSRF
  • + +
    +
    diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.ql b/javascript/ql/src/Security/CWE-918/RequestForgery.ql new file mode 100644 index 000000000000..a76307c439aa --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.ql @@ -0,0 +1,18 @@ +/** + * @name Uncontrolled data used in remote request + * @description Sending remote requests with user-controlled data allows for request forgery attacks. + * @kind problem + * @problem.severity error + * @precision high + * @id js/request-forgery + * @tags security + * external/cwe/cwe-918 + */ + +import javascript +import semmle.javascript.security.dataflow.RequestForgery::RequestForgery + +from Configuration cfg, DataFlow::Node source, Sink sink, DataFlow::Node request +where cfg.hasFlow(source, sink) and + request = sink.getARequest() +select request, "The $@ of this request depends on $@.", sink, sink.getKind(), source, "a user-provided value" diff --git a/javascript/ql/src/Security/CWE-918/examples/RequestForgeryBad.js b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryBad.js new file mode 100644 index 000000000000..d0eb8b56b0fc --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryBad.js @@ -0,0 +1,12 @@ +import http from 'http'; +import url from 'url'; + +var server = http.createServer(function(req, res) { + var target = url.parse(request.url, true).query.target; + + // BAD: `target` is controlled by the attacker + http.get('https://' + target + ".example.com/data/", res => { + // process request response ... + }); + +}); diff --git a/javascript/ql/src/Security/CWE-918/examples/RequestForgeryGood.js b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryGood.js new file mode 100644 index 000000000000..2c1662a5a1e7 --- /dev/null +++ b/javascript/ql/src/Security/CWE-918/examples/RequestForgeryGood.js @@ -0,0 +1,19 @@ +import http from 'http'; +import url from 'url'; + +var server = http.createServer(function(req, res) { + var target = url.parse(request.url, true).query.target; + + var subdomain; + if (target === 'EU') { + subdomain = "europe" + } else { + subdomain = "world" + } + + // GOOD: `subdomain` is controlled by the server + http.get('https://' + subdomain + ".example.com/data/", res => { + // process request response ... + }); + +}); diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll new file mode 100644 index 000000000000..15c239d23765 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll @@ -0,0 +1,87 @@ +/** + * Provides a taint-tracking configuration for reasoning about request forgery. + */ + +import semmle.javascript.security.dataflow.RemoteFlowSources +import UrlConcatenation + +module RequestForgery { + + /** + * A data flow source for request forgery. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for request forgery. + */ + abstract class Sink extends DataFlow::Node { + /** + * Gets a request that uses this sink. + */ + abstract DataFlow::Node getARequest(); + + /** + * Gets the kind of this sink. + */ + abstract string getKind(); + + } + + /** + * A sanitizer for request forgery. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A taint tracking configuration for request forgery. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { + this = "RequestForgery" + } + + override predicate isSource(DataFlow::Node source) { + source instanceof Source + } + + override predicate isSink(DataFlow::Node sink) { + sink instanceof Sink + } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + + override predicate isSanitizer(DataFlow::Node source, DataFlow::Node sink) { + sanitizingPrefixEdge(source, sink) + } + + } + + /** A source of remote user input, considered as a flow source for request forgery. */ + private class RemoteFlowSourceAsSource extends Source { + RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource } + } + + /** + * The URL of a URL request, viewed as a sink for request forgery. + */ + private class UrlRequestUrlAsSink extends Sink { + + UrlRequest request; + + UrlRequestUrlAsSink() { + this = request.getUrl() + } + + override DataFlow::Node getARequest() { + result = request + } + + override string getKind() { + result = "URL" + } + } +} diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected new file mode 100644 index 000000000000..d114b6a8e389 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -0,0 +1,6 @@ +| tst.js:16:5:16:20 | request(tainted) | The $@ of this request depends on $@. | tst.js:16:13:16:19 | tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:18:5:18:24 | request.get(tainted) | The $@ of this request depends on $@. | tst.js:18:17:18:23 | tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:22:5:22:20 | request(options) | The $@ of this request depends on $@. | tst.js:21:19:21:25 | tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:24:5:24:32 | request ... ainted) | The $@ of this request depends on $@. | tst.js:24:13:24:31 | "http://" + tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:26:5:26:43 | request ... ainted) | The $@ of this request depends on $@. | tst.js:26:13:26:42 | "http:/ ... tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | +| tst.js:28:5:28:44 | request ... ainted) | The $@ of this request depends on $@. | tst.js:28:13:28:43 | "http:/ ... tainted | URL | tst.js:12:29:12:35 | req.url | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.qlref b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.qlref new file mode 100644 index 000000000000..fcb4e41daf88 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/RequestForgery.qlref @@ -0,0 +1 @@ +Security/CWE-918/RequestForgery.ql diff --git a/javascript/ql/test/query-tests/Security/CWE-918/tst.js b/javascript/ql/test/query-tests/Security/CWE-918/tst.js new file mode 100644 index 000000000000..b502e56bd12f --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-918/tst.js @@ -0,0 +1,31 @@ +import request from 'request'; +import requestPromise from 'request-promise'; +import superagent from 'superagent'; +import http from 'http'; +import express from 'express'; +import axios from 'axios'; +import got from 'got'; +import nodeFetch from 'node-fetch'; +import url from 'url'; + +var server = http.createServer(function(req, res) { + var tainted = url.parse(req.url, true).query.url; + + request("example.com"); // OK + + request(tainted); // NOT OK + + request.get(tainted); // NOT OK + + var options = {}; + options.url = tainted; + request(options); // NOT OK + + request("http://" + tainted); // NOT OK + + request("http://example.com" + tainted); // NOT OK + + request("http://example.com/" + tainted); // NOT OK + + request("http://example.com/?" + tainted); // OK +}) From 68b7a8b57efc769766997468ef2ded9c0e5c88a0 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 30 Aug 2018 14:26:06 +0200 Subject: [PATCH 03/50] JS: change notes for `UrlRequest` libraries and `js/request-forgery` --- change-notes/1.18/analysis-javascript.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index 733e3b25bade..a7c4cc4b944e 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -19,11 +19,13 @@ * Type inference for simple function calls has been improved. This may give additional results for queries that rely on type inference. * Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following libraries: + - [axios](https://github.com/axios/axios) - [bluebird](https://bluebirdjs.com) - [browserid-crypto](https://github.com/mozilla/browserid-crypto) - [compose-function](https://github.com/stoeffel/compose-function) - [cookie-parser](https://github.com/expressjs/cookie-parser) - [cookie-session](https://github.com/expressjs/cookie-session) + - [cross-fetch](https://github.com/lquixada/cross-fetch) - [crypto-js](https://github.com/https://github.com/brix/crypto-js) - [deep-assign](https://github.com/sindresorhus/deep-assign) - [deep-extend](https://github.com/unclechu/node-deep-extend) @@ -45,9 +47,11 @@ - [fast-json-parse](https://github.com/mcollina/fast-json-parse) - [forge](https://github.com/digitalbazaar/forge) - [format-util](https://github.com/tmpfs/format-util) + - [got](https://github.com/sindresorhus/got) - [global](https://github.com/Raynos/global) - [he](https://github.com/mathiasbynens/he) - [html-entities](https://github.com/mdevils/node-html-entities) + - [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) - [jquery](https://jquery.com) - [js-extend](https://github.com/vmattos/js-extend) - [json-parse-better-errors](https://github.com/zkat/json-parse-better-errors) @@ -63,6 +67,7 @@ - [mixin-object](https://github.com/jonschlinkert/mixin-object) - [MySQL2](https://github.com/sidorares/node-mysql2) - [node.extend](https://github.com/dreamerslab/node.extend) + - [node-fetch](https://github.com/bitinn/node-fetch) - [object-assign](https://github.com/sindresorhus/object-assign) - [object.assign](https://github.com/ljharb/object.assign) - [object.defaults](https://github.com/jonschlinkert/object.defaults) @@ -71,6 +76,10 @@ - [printj](https://github.com/SheetJS/printj) - [q](https://documentup.com/kriskowal/q/) - [ramda](https://ramdajs.com) + - [request](https://github.com/request/request) + - [request-promise](https://github.com/request/request-promise) + - [request-promise-any](https://github.com/request/request-promise-any) + - [request-promise-native](https://github.com/request/request-promise-native) - [React Native](https://facebook.github.io/react-native/) - [safe-json-parse](https://github.com/Raynos/safe-json-parse) - [sanitize](https://github.com/pocketly/node-sanitize) @@ -78,6 +87,7 @@ - [smart-extend](https://github.com/danielkalen/smart-extend) - [sprintf.js](https://github.com/alexei/sprintf.js) - [string-template](https://github.com/Matt-Esch/string-template) + - [superagent](https://github.com/visionmedia/superagent) - [underscore](https://underscorejs.org) - [util-extend](https://github.com/isaacs/util-extend) - [utils-merge](https://github.com/jaredhanson/utils-merge) @@ -92,6 +102,7 @@ | Clear-text logging of sensitive information (`js/clear-text-logging`) | security, external/cwe/cwe-312, external/cwe/cwe-315, external/cwe/cwe-359 | Highlights logging of sensitive information, indicating a violation of [CWE-312](https://cwe.mitre.org/data/definitions/312.html). Results shown on LGTM by default. | | Disabling Electron webSecurity (`js/disabling-electron-websecurity`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `webSecurity` property set to false. Results shown on LGTM by default. | | Enabling Electron allowRunningInsecureContent (`js/enabling-electron-insecure-content`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `allowRunningInsecureContent` property set to true. Results shown on LGTM by default. | +| Uncontrolled data used in remote request (`js/request-forgery`) | security, external/cwe/cwe-918 | Highlights remote requests that are built from unsanitized user input, indicating a violation of [CWE-918](https://cwe.mitre.org/data/definitions/918.html). Results shown on LGTM by default. | | Use of externally-controlled format string (`js/tainted-format-string`) | security, external/cwe/cwe-134 | Highlights format strings containing user-provided data, indicating a violation of [CWE-134](https://cwe.mitre.org/data/definitions/134.html). Results shown on LGTM by default. | ## Changes to existing queries From 80b81b07c5ac09ce8f54c2a48c9e77970e1f930e Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 12:56:28 +0200 Subject: [PATCH 04/50] JS: refactor DefaultUrlRequest: extract names --- .../javascript/frameworks/UrlRequests.qll | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index e23d19fb287f..ab4d2335690a 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -37,6 +37,21 @@ class UrlRequest extends DataFlow::CallNode { } } +/** + * Gets name of an HTTP request method, in all-lowercase. + */ +private string httpMethodName() { + result = any(HTTP::RequestMethodName m).toLowerCase() +} + +/** + * Gets the name of a property that likely contains a URL value. + */ +private string urlPropertyName() { + result = "uri" or + result = "url" +} + /** * A simple model of common URL request libraries. */ @@ -45,9 +60,7 @@ private class DefaultUrlRequest extends CustomUrlRequest { DataFlow::Node url; DefaultUrlRequest() { - exists (string moduleName, DataFlow::SourceNode callee, string httpMethodName, string urlName | - httpMethodName = any(HTTP::RequestMethodName m).toLowerCase() and - (urlName = "url" or urlName = "uri") and // slightly over-approximate, in the name of simplicity + exists (string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | ( ( @@ -58,23 +71,23 @@ private class DefaultUrlRequest extends CustomUrlRequest { ) and ( callee = DataFlow::moduleImport(moduleName) or - callee = DataFlow::moduleMember(moduleName, httpMethodName) + callee = DataFlow::moduleMember(moduleName, httpMethodName()) ) and ( url = getArgument(0) or - url = getOptionArgument(0, urlName) + url = getOptionArgument(0, urlPropertyName()) ) ) or ( moduleName = "superagent" and - callee = DataFlow::moduleMember(moduleName, httpMethodName) and + callee = DataFlow::moduleMember(moduleName, httpMethodName()) and url = getArgument(0) ) or ( (moduleName = "http" or moduleName = "https") and - callee = DataFlow::moduleMember(moduleName, httpMethodName) and + callee = DataFlow::moduleMember(moduleName, httpMethodName()) and url = getArgument(0) ) or @@ -82,12 +95,12 @@ private class DefaultUrlRequest extends CustomUrlRequest { moduleName = "axios" and ( callee = DataFlow::moduleImport(moduleName) or - callee = DataFlow::moduleMember(moduleName, httpMethodName) or + callee = DataFlow::moduleMember(moduleName, httpMethodName()) or callee = DataFlow::moduleMember(moduleName, "request") ) and ( url = getArgument(0) or - url = getOptionArgument([0..2], urlName) // slightly over-approximate, in the name of simplicity + url = getOptionArgument([0..2], urlPropertyName()) // slightly over-approximate, in the name of simplicity ) ) or From d7a81ef8ef1d70012ae50c0908784e14ae654be5 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 12:58:07 +0200 Subject: [PATCH 05/50] JS: refactor DefaultUrlRequest: extract the `request` library --- .../javascript/frameworks/UrlRequests.qll | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index ab4d2335690a..96dc0e3af4b7 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -62,23 +62,6 @@ private class DefaultUrlRequest extends CustomUrlRequest { DefaultUrlRequest() { exists (string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | - ( - ( - moduleName = "request" or - moduleName = "request-promise" or - moduleName = "request-promise-any" or - moduleName = "request-promise-native" - ) and - ( - callee = DataFlow::moduleImport(moduleName) or - callee = DataFlow::moduleMember(moduleName, httpMethodName()) - ) and - ( - url = getArgument(0) or - url = getOptionArgument(0, urlPropertyName()) - ) - ) - or ( moduleName = "superagent" and callee = DataFlow::moduleMember(moduleName, httpMethodName()) and @@ -137,4 +120,38 @@ private class DefaultUrlRequest extends CustomUrlRequest { result = url } +} + + +/** + * A model of a URL request in the `request` library. + */ +private class RequestUrlRequest extends CustomUrlRequest { + + DataFlow::Node url; + + RequestUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + ( + moduleName = "request" or + moduleName = "request-promise" or + moduleName = "request-promise-any" or + moduleName = "request-promise-native" + ) and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, httpMethodName()) + ) and + ( + url = getArgument(0) or + url = getOptionArgument(0, urlPropertyName()) + ) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + } \ No newline at end of file From b3b997ca9107472b67b3c546d7061cc85aa75c57 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 12:59:58 +0200 Subject: [PATCH 06/50] JS: refactor DefaultUrlRequest: extract the `axios` library --- .../javascript/frameworks/UrlRequests.qll | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index 96dc0e3af4b7..bc7a49c15dc4 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -74,19 +74,6 @@ private class DefaultUrlRequest extends CustomUrlRequest { url = getArgument(0) ) or - ( - moduleName = "axios" and - ( - callee = DataFlow::moduleImport(moduleName) or - callee = DataFlow::moduleMember(moduleName, httpMethodName()) or - callee = DataFlow::moduleMember(moduleName, "request") - ) and - ( - url = getArgument(0) or - url = getOptionArgument([0..2], urlPropertyName()) // slightly over-approximate, in the name of simplicity - ) - ) - or ( moduleName = "got" and ( @@ -154,4 +141,33 @@ private class RequestUrlRequest extends CustomUrlRequest { result = url } -} \ No newline at end of file +} + +/** + * A model of a URL request in the `axios` library. + */ +private class AxiosUrlRequest extends CustomUrlRequest { + + DataFlow::Node url; + + AxiosUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + moduleName = "axios" and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, httpMethodName()) or + callee = DataFlow::moduleMember(moduleName, "request") + ) and + ( + url = getArgument(0) or + url = getOptionArgument([0..2], urlPropertyName()) // slightly over-approximate, in the name of simplicity + ) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} From 5f26c23582c84f73cdf882514ce318a51e0ee562 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 13:02:48 +0200 Subject: [PATCH 07/50] JS: refactor DefaultUrlRequest: extract the `fetch` API --- .../javascript/frameworks/UrlRequests.qll | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index bc7a49c15dc4..437266b808cd 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -84,23 +84,7 @@ private class DefaultUrlRequest extends CustomUrlRequest { url = getArgument(0) and not exists (getOptionArgument(1, "baseUrl")) ) ) - or - ( - ( - moduleName = "node-fetch" or - moduleName = "cross-fetch" or - moduleName = "isomorphic-fetch" - ) and - callee = DataFlow::moduleImport(moduleName) and - url = getArgument(0) - ) - ) - or - ( - this = DataFlow::globalVarRef("fetch").getACall() and - url = getArgument(0) ) - } override DataFlow::Node getUrl() { @@ -171,3 +155,34 @@ private class AxiosUrlRequest extends CustomUrlRequest { } } + +/** + * A model of a URL request in an implementation of the `fetch` API. + */ +private class FetchUrlRequest extends CustomUrlRequest { + + DataFlow::Node url; + + FetchUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + ( + moduleName = "node-fetch" or + moduleName = "cross-fetch" or + moduleName = "isomorphic-fetch" + ) and + callee = DataFlow::moduleImport(moduleName) and + url = getArgument(0) + ) + or + ( + this = DataFlow::globalVarRef("fetch").getACall() and + url = getArgument(0) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} From 1abdf2ffd578ef4e2f94d7d19c0812b10f4f9a45 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 13:07:02 +0200 Subject: [PATCH 08/50] JS: refactor DefaultUrlRequest: extract the `http` library --- .../javascript/frameworks/UrlRequests.qll | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index 437266b808cd..7de67faf1616 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -68,12 +68,6 @@ private class DefaultUrlRequest extends CustomUrlRequest { url = getArgument(0) ) or - ( - (moduleName = "http" or moduleName = "https") and - callee = DataFlow::moduleMember(moduleName, httpMethodName()) and - url = getArgument(0) - ) - or ( moduleName = "got" and ( @@ -186,3 +180,26 @@ private class FetchUrlRequest extends CustomUrlRequest { } } + + +/** + * A model of a URL request in the Node.js `http` library. + */ +private class NodeHttpUrlRequest extends CustomUrlRequest { + + DataFlow::Node url; + + NodeHttpUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + (moduleName = "http" or moduleName = "https") and + callee = DataFlow::moduleMember(moduleName, httpMethodName()) and + url = getArgument(0) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} From de6b83548ae182187d971ee7cbc31045ea6c41bb Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 13:08:31 +0200 Subject: [PATCH 09/50] JS: refactor DefaultUrlRequest: extract the `got` library --- .../javascript/frameworks/UrlRequests.qll | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index 7de67faf1616..0f7bfafd1820 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -67,17 +67,6 @@ private class DefaultUrlRequest extends CustomUrlRequest { callee = DataFlow::moduleMember(moduleName, httpMethodName()) and url = getArgument(0) ) - or - ( - moduleName = "got" and - ( - callee = DataFlow::moduleImport(moduleName) or - callee = DataFlow::moduleMember(moduleName, "stream") - ) and - ( - url = getArgument(0) and not exists (getOptionArgument(1, "baseUrl")) - ) - ) ) } @@ -203,3 +192,29 @@ private class NodeHttpUrlRequest extends CustomUrlRequest { } } + + +/** + * A model of a URL request in the `got` library. + */ +private class GotUrlRequest extends CustomUrlRequest { + + DataFlow::Node url; + + GotUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + moduleName = "got" and + ( + callee = DataFlow::moduleImport(moduleName) or + callee = DataFlow::moduleMember(moduleName, "stream") + ) and + url = getArgument(0) and not exists (getOptionArgument(1, "baseUrl")) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} From 0a89f1a4202d182301b1ba0b14f6cf45f184f907 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 13:09:57 +0200 Subject: [PATCH 10/50] JS: eliminate DefaultUrlRequest: extract the `got` library --- .../javascript/frameworks/UrlRequests.qll | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index 0f7bfafd1820..dad03577acd6 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -52,31 +52,6 @@ private string urlPropertyName() { result = "url" } -/** - * A simple model of common URL request libraries. - */ -private class DefaultUrlRequest extends CustomUrlRequest { - - DataFlow::Node url; - - DefaultUrlRequest() { - exists (string moduleName, DataFlow::SourceNode callee | - this = callee.getACall() | - ( - moduleName = "superagent" and - callee = DataFlow::moduleMember(moduleName, httpMethodName()) and - url = getArgument(0) - ) - ) - } - - override DataFlow::Node getUrl() { - result = url - } - -} - - /** * A model of a URL request in the `request` library. */ @@ -218,3 +193,25 @@ private class GotUrlRequest extends CustomUrlRequest { } } + +/** + * A model of a URL request in the `superagent` library. + */ +private class SuperAgentUrlRequest extends CustomUrlRequest { + + DataFlow::Node url; + + SuperAgentUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + moduleName = "superagent" and + callee = DataFlow::moduleMember(moduleName, httpMethodName()) and + url = getArgument(0) + ) + } + + override DataFlow::Node getUrl() { + result = url + } + +} From cb2a6ede590072951682396c50920a603c6102c5 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 13:15:53 +0200 Subject: [PATCH 11/50] JS: support http.request URL requests --- .../ql/src/semmle/javascript/frameworks/UrlRequests.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index dad03577acd6..9ae388cdb5bf 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -157,7 +157,11 @@ private class NodeHttpUrlRequest extends CustomUrlRequest { exists (string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | (moduleName = "http" or moduleName = "https") and - callee = DataFlow::moduleMember(moduleName, httpMethodName()) and + ( + callee = DataFlow::moduleMember(moduleName, httpMethodName()) + or + callee = DataFlow::moduleMember(moduleName, "request") + ) and url = getArgument(0) ) } From 003b600e2478fb0aaf4db19b61c782d30cae5156 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 4 Sep 2018 13:11:52 +0100 Subject: [PATCH 12/50] TypeScript: disable queries that rely on token information --- javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql | 1 + .../ql/src/LanguageFeatures/SemicolonInsertion.ql | 3 ++- .../MisleadingIndentationAfterControlStmt.ql | 3 ++- .../SemicolonInsertion/template_literal.ts | 12 ++++++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts diff --git a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql index 48ae48112446..70c89715bf85 100644 --- a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql +++ b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql @@ -45,4 +45,5 @@ class OmittedArrayElement extends ArrayExpr { } from OmittedArrayElement ae +where not ae.getFile().getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer select ae, "Avoid omitted array elements." \ No newline at end of file diff --git a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql index fc52539be8db..2eef68fe5f4f 100644 --- a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql +++ b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql @@ -36,7 +36,8 @@ where s.hasSemicolonInserted() and asi = strictcount(Stmt ss | asi(sc, ss, true)) and nstmt = strictcount(Stmt ss | asi(sc, ss, _)) and perc = ((1-asi/nstmt)*100).floor() and - perc >= 90 + perc >= 90 and + not s.getFile().getFileType().isTypeScript() // ignore some quirks in the TypeScript tokenizer select (LastLineOf)s, "Avoid automated semicolon insertion " + "(" + perc + "% of all statements in $@ have an explicit semicolon).", sc, "the enclosing " + sctype \ No newline at end of file diff --git a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql index 0629224ed503..84e11138f7bc 100644 --- a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql +++ b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql @@ -39,6 +39,7 @@ where misleadingIndentationCandidate(ctrl, s1, s2) and f.hasIndentation(ctrlStartLine, indent, _) and f.hasIndentation(startLine1, indent, _) and f.hasIndentation(startLine2, indent, _) and - not s2 instanceof EmptyStmt + not s2 instanceof EmptyStmt and + not f.getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer select (FirstLineOf)s2, "The indentation of this statement suggests that it is controlled by $@, while in fact it is not.", (FirstLineOf)ctrl, "this statement" \ No newline at end of file diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts b/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts new file mode 100644 index 000000000000..01ce984f8e30 --- /dev/null +++ b/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts @@ -0,0 +1,12 @@ +function foo(arg) { + console.log(arg); + console.log(arg); + console.log(arg); + console.log(arg); + console.log(arg); + console.log(arg); + console.log(arg); + console.log(arg); + console.log(arg); + console.log(`Unknown option '${arg}'.`); +} From 2b8bc63b01b6da9dd5d85204ce9c16c001b63445 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 4 Sep 2018 14:23:37 +0100 Subject: [PATCH 13/50] TypeScript: add change note --- change-notes/1.18/analysis-javascript.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index 733e3b25bade..efbc286cd686 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -108,6 +108,7 @@ | Missing rate limiting | More true-positive results, fewer false-positive results | This rule now recognizes additional rate limiters and expensive route handlers. | | Missing X-Frame-Options HTTP header | Fewer false-positive results | This rule now treats header names case-insensitively. | | Reflected cross-site scripting | Fewer false-positive results | This rule now treats header names case-insensitively. | +| Semicolon insertion | Fewer results | This rule now ignores TypeScript files as it did not work correctly. | | Server-side URL redirect | More true-positive results | This rule now treats header names case-insensitively. | | Superfluous trailing arguments | Fewer false-positive results | This rule now ignores calls to some empty functions. | | Type confusion through parameter tampering | Fewer false-positive results | This rule no longer flags emptiness checks. | From 6ceb10371a34415f2b12d8aa64f2f48e5661d48c Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 4 Sep 2018 15:06:04 +0100 Subject: [PATCH 14/50] TypeScript: rephrase change note --- change-notes/1.18/analysis-javascript.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index efbc286cd686..74759bf2ba9e 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -105,10 +105,12 @@ | Hard-coded credentials | More true-positive results | This rule now recognizes secret cryptographic keys. | | Incomplete string escaping or encoding | Better name, more true-positive results | This rule has been renamed to more clearly reflect its purpose. Also, it now recognizes incomplete URL encoding and decoding. | | Insecure randomness | More true-positive results | This rule now recognizes secret cryptographic keys. | +| Misleading indentation after control statement | Fewer results | This rule temporarily ignores TypeScript files. | | Missing rate limiting | More true-positive results, fewer false-positive results | This rule now recognizes additional rate limiters and expensive route handlers. | | Missing X-Frame-Options HTTP header | Fewer false-positive results | This rule now treats header names case-insensitively. | +| Omitted array element | Fewer results | This rule temporarily ignores TypeScript files. | | Reflected cross-site scripting | Fewer false-positive results | This rule now treats header names case-insensitively. | -| Semicolon insertion | Fewer results | This rule now ignores TypeScript files as it did not work correctly. | +| Semicolon insertion | Fewer results | This rule temporarily ignores TypeScript files. | | Server-side URL redirect | More true-positive results | This rule now treats header names case-insensitively. | | Superfluous trailing arguments | Fewer false-positive results | This rule now ignores calls to some empty functions. | | Type confusion through parameter tampering | Fewer false-positive results | This rule no longer flags emptiness checks. | From e0c073360bc1cda6d318c34cc184cbac47ff8857 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Sat, 1 Sep 2018 21:01:13 +0200 Subject: [PATCH 15/50] C++: Remove CP in getOperandMemoryAccess The overrides of `Instruction.getOperandMemoryAccess` did not relate `this` to any of its other parameters, which made it attempt to compute the Cartesian product of `Instruction` and `TPhiOperand`. This happened only during computation of aliased SSA. Perhaps the optimizer was able to eliminate the CP for the non-aliased SSA computation. With this change, I'm able to compute aliased SSA for medium-sized snapshots. --- .../cpp/ir/implementation/aliased_ssa/Instruction.qll | 10 +++++----- .../code/cpp/ir/implementation/raw/Instruction.qll | 10 +++++----- .../ir/implementation/unaliased_ssa/Instruction.qll | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index 660c4ac3c9c6..aee11acca1a9 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -602,7 +602,7 @@ class ReturnValueInstruction extends ReturnInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof ReturnValueOperand and + exists(this.getOperand(tag.(ReturnValueOperand))) and result instanceof IndirectMemoryAccess } } @@ -629,7 +629,7 @@ class LoadInstruction extends CopyInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof CopySourceOperand and + exists(this.getOperand(tag.(CopySourceOperand))) and result instanceof IndirectMemoryAccess } @@ -1015,7 +1015,7 @@ class ThrowValueInstruction extends ThrowInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof ExceptionOperand and + exists(this.getOperand(tag.(ExceptionOperand))) and result instanceof IndirectMemoryAccess } @@ -1114,7 +1114,7 @@ class UnmodeledUseInstruction extends Instruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof UnmodeledUseOperand and + exists(this.getOperand(tag.(UnmodeledUseOperand))) and result instanceof UnmodeledMemoryAccess } } @@ -1125,7 +1125,7 @@ class PhiInstruction extends Instruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof PhiOperand and + exists(this.getOperand(tag.(PhiOperand))) and result instanceof PhiMemoryAccess } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index 660c4ac3c9c6..aee11acca1a9 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -602,7 +602,7 @@ class ReturnValueInstruction extends ReturnInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof ReturnValueOperand and + exists(this.getOperand(tag.(ReturnValueOperand))) and result instanceof IndirectMemoryAccess } } @@ -629,7 +629,7 @@ class LoadInstruction extends CopyInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof CopySourceOperand and + exists(this.getOperand(tag.(CopySourceOperand))) and result instanceof IndirectMemoryAccess } @@ -1015,7 +1015,7 @@ class ThrowValueInstruction extends ThrowInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof ExceptionOperand and + exists(this.getOperand(tag.(ExceptionOperand))) and result instanceof IndirectMemoryAccess } @@ -1114,7 +1114,7 @@ class UnmodeledUseInstruction extends Instruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof UnmodeledUseOperand and + exists(this.getOperand(tag.(UnmodeledUseOperand))) and result instanceof UnmodeledMemoryAccess } } @@ -1125,7 +1125,7 @@ class PhiInstruction extends Instruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof PhiOperand and + exists(this.getOperand(tag.(PhiOperand))) and result instanceof PhiMemoryAccess } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index 660c4ac3c9c6..aee11acca1a9 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -602,7 +602,7 @@ class ReturnValueInstruction extends ReturnInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof ReturnValueOperand and + exists(this.getOperand(tag.(ReturnValueOperand))) and result instanceof IndirectMemoryAccess } } @@ -629,7 +629,7 @@ class LoadInstruction extends CopyInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof CopySourceOperand and + exists(this.getOperand(tag.(CopySourceOperand))) and result instanceof IndirectMemoryAccess } @@ -1015,7 +1015,7 @@ class ThrowValueInstruction extends ThrowInstruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof ExceptionOperand and + exists(this.getOperand(tag.(ExceptionOperand))) and result instanceof IndirectMemoryAccess } @@ -1114,7 +1114,7 @@ class UnmodeledUseInstruction extends Instruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof UnmodeledUseOperand and + exists(this.getOperand(tag.(UnmodeledUseOperand))) and result instanceof UnmodeledMemoryAccess } } @@ -1125,7 +1125,7 @@ class PhiInstruction extends Instruction { } override final MemoryAccessKind getOperandMemoryAccess(OperandTag tag) { - tag instanceof PhiOperand and + exists(this.getOperand(tag.(PhiOperand))) and result instanceof PhiMemoryAccess } From 6d78350fee98b51c67b401a46c50356ad47a77b0 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 13:56:42 +0200 Subject: [PATCH 16/50] JS: s/URLRequest/ClientRequest, merge with NodeJSLib::ClientRequest --- .../semmle/javascript/frameworks/Electron.qll | 10 +-- .../javascript/frameworks/NodeJSLib.qll | 66 ++++++++----------- .../javascript/frameworks/UrlRequests.qll | 52 ++++----------- .../security/dataflow/RequestForgery.qll | 6 +- .../frameworks/Electron/ClientRequest.ql | 2 +- .../frameworks/NodeJSLib/ClientRequest.ql | 2 +- .../frameworks/UrlRequests/UrlRequest.ql | 2 +- 7 files changed, 51 insertions(+), 89 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll index a60309c9be10..a2b4e6745d6c 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll @@ -37,12 +37,12 @@ module Electron { /** * A Node.js-style HTTP or HTTPS request made using an Electron module. */ - abstract class ClientRequest extends NodeJSLib::ClientRequest {} + abstract class ElectronClientRequest extends NodeJSLib::NodeJSClientRequest {} /** * A Node.js-style HTTP or HTTPS request made using `electron.net`, for example `net.request(url)`. */ - private class NetRequest extends ClientRequest { + private class NetRequest extends ElectronClientRequest { NetRequest() { this = DataFlow::moduleMember("electron", "net").getAMemberCall("request") } @@ -56,7 +56,7 @@ module Electron { /** * A Node.js-style HTTP or HTTPS request made using `electron.client`, for example `new client(url)`. */ - private class NewClientRequest extends ClientRequest { + private class NewClientRequest extends ElectronClientRequest { NewClientRequest() { this = DataFlow::moduleMember("electron", "ClientRequest").getAnInstantiation() } @@ -75,12 +75,12 @@ module Electron { exists(NodeJSLib::ClientRequestHandler handler | this = handler.getParameter(0) and handler.getAHandledEvent() = "redirect" and - handler.getClientRequest() instanceof ClientRequest + handler.getClientRequest() instanceof ElectronClientRequest ) } override string getSourceType() { - result = "Electron ClientRequest redirect event" + result = "ElectronClientRequest redirect event" } } } \ No newline at end of file diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 6a11bef5edc5..6f806d723d70 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -504,7 +504,8 @@ module NodeJSLib { /** * A data flow node that is an HTTP or HTTPS client request made by a Node.js server, for example `http.request(url)`. */ - abstract class ClientRequest extends DataFlow::DefaultSourceNode { + abstract class NodeJSClientRequest extends DataFlow::DefaultSourceNode { + /** * Gets the options object or string URL used to make the request. */ @@ -512,40 +513,29 @@ module NodeJSLib { } /** - * A data flow node that is an HTTP or HTTPS client request made by a Node.js server, for example `http.request(url)`. + * A model of a URL request in the Node.js `http` library. */ - private class HttpRequest extends ClientRequest { - HttpRequest() { - exists(string protocol | + private class NodeHttpUrlRequest extends CustomClientRequest, NodeJSClientRequest { + + DataFlow::Node url; + + NodeHttpUrlRequest() { + exists (string moduleName, DataFlow::SourceNode callee | + this = callee.getACall() | + (moduleName = "http" or moduleName = "https") and ( - protocol = "http" or - protocol = "https" - ) - and - this = DataFlow::moduleImport(protocol).getAMemberCall("request") + callee = DataFlow::moduleMember(moduleName, any(HTTP::RequestMethodName m).toLowerCase()) + or + callee = DataFlow::moduleMember(moduleName, "request") + ) and + url = getArgument(0) ) } - - override DataFlow::Node getOptions() { - result = this.(DataFlow::MethodCallNode).getArgument(0) - } - } - - /** - * A data flow node that is an HTTP or HTTPS client request made by a Node.js process, for example `https.get(url)`. - */ - private class HttpGet extends ClientRequest { - HttpGet() { - exists(string protocol | - ( - protocol = "http" or - protocol = "https" - ) - and - this = DataFlow::moduleImport(protocol).getAMemberCall("get") - ) + + override DataFlow::Node getUrl() { + result = url } - + override DataFlow::Node getOptions() { result = this.(DataFlow::MethodCallNode).getArgument(0) } @@ -556,13 +546,13 @@ module NodeJSLib { */ private class ClientRequestCallbackParam extends DataFlow::ParameterNode, RemoteFlowSource { ClientRequestCallbackParam() { - exists(ClientRequest req | + exists(NodeJSClientRequest req | this = req.(DataFlow::MethodCallNode).getCallback(1).getParameter(0) ) } override string getSourceType() { - result = "ClientRequest callback parameter" + result = "NodeJSClientRequest callback parameter" } } @@ -589,7 +579,7 @@ module NodeJSLib { */ class ClientRequestHandler extends DataFlow::FunctionNode { string handledEvent; - ClientRequest clientRequest; + NodeJSClientRequest clientRequest; ClientRequestHandler() { exists(DataFlow::MethodCallNode mcn | @@ -609,7 +599,7 @@ module NodeJSLib { /** * Gets a request this callback is registered for. */ - ClientRequest getClientRequest() { + NodeJSClientRequest getClientRequest() { result = clientRequest } } @@ -626,7 +616,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest response event" + result = "NodeJSClientRequest response event" } } @@ -643,7 +633,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest data event" + result = "NodeJSClientRequest data event" } } @@ -667,7 +657,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest login event" + result = "NodeJSClientRequest login event" } } @@ -725,7 +715,7 @@ module NodeJSLib { } override string getSourceType() { - result = "ClientRequest error event" + result = "NodeJSClientRequest error event" } } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll index 9ae388cdb5bf..19bb80d4fd2e 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll @@ -1,8 +1,8 @@ /** - * Provides classes for modelling URL requests. + * Provides classes for modelling the client-side of a URL request. * - * Subclass `UrlRequest` to refine the behavior of the analysis on existing URL requests. - * Subclass `CustomUrlRequest` to introduce new kinds of URL requests. + * Subclass `ClientRequest` to refine the behavior of the analysis on existing client requests. + * Subclass `CustomClientRequest` to introduce new kinds of client requests. */ import javascript @@ -10,7 +10,7 @@ import javascript /** * A call that performs a request to a URL. */ -class CustomUrlRequest extends DataFlow::CallNode { +class CustomClientRequest extends DataFlow::CallNode { /** * Gets the URL of the request. @@ -21,11 +21,11 @@ class CustomUrlRequest extends DataFlow::CallNode { /** * A call that performs a request to a URL. */ -class UrlRequest extends DataFlow::CallNode { +class ClientRequest extends DataFlow::CallNode { - CustomUrlRequest custom; + CustomClientRequest custom; - UrlRequest() { + ClientRequest() { this = custom } @@ -55,7 +55,7 @@ private string urlPropertyName() { /** * A model of a URL request in the `request` library. */ -private class RequestUrlRequest extends CustomUrlRequest { +private class RequestUrlRequest extends CustomClientRequest { DataFlow::Node url; @@ -88,7 +88,7 @@ private class RequestUrlRequest extends CustomUrlRequest { /** * A model of a URL request in the `axios` library. */ -private class AxiosUrlRequest extends CustomUrlRequest { +private class AxiosUrlRequest extends CustomClientRequest { DataFlow::Node url; @@ -117,7 +117,7 @@ private class AxiosUrlRequest extends CustomUrlRequest { /** * A model of a URL request in an implementation of the `fetch` API. */ -private class FetchUrlRequest extends CustomUrlRequest { +private class FetchUrlRequest extends CustomClientRequest { DataFlow::Node url; @@ -145,38 +145,10 @@ private class FetchUrlRequest extends CustomUrlRequest { } - -/** - * A model of a URL request in the Node.js `http` library. - */ -private class NodeHttpUrlRequest extends CustomUrlRequest { - - DataFlow::Node url; - - NodeHttpUrlRequest() { - exists (string moduleName, DataFlow::SourceNode callee | - this = callee.getACall() | - (moduleName = "http" or moduleName = "https") and - ( - callee = DataFlow::moduleMember(moduleName, httpMethodName()) - or - callee = DataFlow::moduleMember(moduleName, "request") - ) and - url = getArgument(0) - ) - } - - override DataFlow::Node getUrl() { - result = url - } - -} - - /** * A model of a URL request in the `got` library. */ -private class GotUrlRequest extends CustomUrlRequest { +private class GotUrlRequest extends CustomClientRequest { DataFlow::Node url; @@ -201,7 +173,7 @@ private class GotUrlRequest extends CustomUrlRequest { /** * A model of a URL request in the `superagent` library. */ -private class SuperAgentUrlRequest extends CustomUrlRequest { +private class SuperAgentUrlRequest extends CustomClientRequest { DataFlow::Node url; diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll index 15c239d23765..6881382672ab 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/RequestForgery.qll @@ -68,11 +68,11 @@ module RequestForgery { /** * The URL of a URL request, viewed as a sink for request forgery. */ - private class UrlRequestUrlAsSink extends Sink { + private class ClientRequestUrlAsSink extends Sink { - UrlRequest request; + ClientRequest request; - UrlRequestUrlAsSink() { + ClientRequestUrlAsSink() { this = request.getUrl() } diff --git a/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql index 157b158d45dc..ef6eac9f2d4e 100644 --- a/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql +++ b/javascript/ql/test/library-tests/frameworks/Electron/ClientRequest.ql @@ -1,4 +1,4 @@ import javascript -from NodeJSLib::ClientRequest cr +from Electron::ElectronClientRequest cr select cr \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql index 157b158d45dc..0813343a6928 100644 --- a/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql +++ b/javascript/ql/test/library-tests/frameworks/NodeJSLib/ClientRequest.ql @@ -1,4 +1,4 @@ import javascript -from NodeJSLib::ClientRequest cr +from NodeJSLib::NodeJSClientRequest cr select cr \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql b/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql index a1a4de127136..00dae0b3b732 100644 --- a/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql +++ b/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql @@ -1,4 +1,4 @@ import javascript -from UrlRequest r +from ClientRequest r select r, r.getUrl() \ No newline at end of file From 0da14fccbd88d0932c5ff4287672f406c8e34fd9 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 14:00:04 +0200 Subject: [PATCH 17/50] JS: renaming UrlRequests.qll -> ClientRequests.qll --- javascript/ql/src/javascript.qll | 2 +- .../frameworks/{UrlRequests.qll => ClientRequests.qll} | 0 .../ClientRequest.expected} | 0 .../UrlRequest.ql => ClientRequests/ClientRequest.ql} | 0 .../frameworks/{UrlRequests => ClientRequests}/tst.js | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename javascript/ql/src/semmle/javascript/frameworks/{UrlRequests.qll => ClientRequests.qll} (100%) rename javascript/ql/test/library-tests/frameworks/{UrlRequests/UrlRequest.expected => ClientRequests/ClientRequest.expected} (100%) rename javascript/ql/test/library-tests/frameworks/{UrlRequests/UrlRequest.ql => ClientRequests/ClientRequest.ql} (100%) rename javascript/ql/test/library-tests/frameworks/{UrlRequests => ClientRequests}/tst.js (100%) diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index de99ecb0678b..16bdff91f76a 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -54,6 +54,7 @@ import semmle.javascript.frameworks.AWS import semmle.javascript.frameworks.Azure import semmle.javascript.frameworks.Babel import semmle.javascript.frameworks.ComposedFunctions +import semmle.javascript.frameworks.ClientRequests import semmle.javascript.frameworks.Credentials import semmle.javascript.frameworks.CryptoLibraries import semmle.javascript.frameworks.DigitalOcean @@ -71,7 +72,6 @@ import semmle.javascript.frameworks.Request import semmle.javascript.frameworks.SQL import semmle.javascript.frameworks.StringFormatters import semmle.javascript.frameworks.UriLibraries -import semmle.javascript.frameworks.UrlRequests import semmle.javascript.frameworks.XmlParsers import semmle.javascript.frameworks.xUnit import semmle.javascript.linters.ESLint diff --git a/javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll similarity index 100% rename from javascript/ql/src/semmle/javascript/frameworks/UrlRequests.qll rename to javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll diff --git a/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected similarity index 100% rename from javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.expected rename to javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected diff --git a/javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql similarity index 100% rename from javascript/ql/test/library-tests/frameworks/UrlRequests/UrlRequest.ql rename to javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql diff --git a/javascript/ql/test/library-tests/frameworks/UrlRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js similarity index 100% rename from javascript/ql/test/library-tests/frameworks/UrlRequests/tst.js rename to javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js From d578c7422d87449a1446e3f2c24c1c15063adff3 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 14:12:49 +0200 Subject: [PATCH 18/50] JS: docstring cleanup --- javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 6f806d723d70..58c98273c8b0 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -502,7 +502,7 @@ module NodeJSLib { } /** - * A data flow node that is an HTTP or HTTPS client request made by a Node.js server, for example `http.request(url)`. + * A data flow node that is an HTTP or HTTPS client request made by a Node.js application, for example `http.request(url)`. */ abstract class NodeJSClientRequest extends DataFlow::DefaultSourceNode { From 2dd8e95a5109227a7b914b13c32896ef900dcb1f Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 14:13:52 +0200 Subject: [PATCH 19/50] JS: remove unused `getOptions` method --- .../ql/src/semmle/javascript/frameworks/Electron.qll | 8 -------- .../ql/src/semmle/javascript/frameworks/NodeJSLib.qll | 7 ------- 2 files changed, 15 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll index a2b4e6745d6c..13eba4772c57 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll @@ -46,10 +46,6 @@ module Electron { NetRequest() { this = DataFlow::moduleMember("electron", "net").getAMemberCall("request") } - - override DataFlow::Node getOptions() { - result = this.(DataFlow::MethodCallNode).getArgument(0) - } } @@ -60,10 +56,6 @@ module Electron { NewClientRequest() { this = DataFlow::moduleMember("electron", "ClientRequest").getAnInstantiation() } - - override DataFlow::Node getOptions() { - result = this.(DataFlow::NewNode).getArgument(0) - } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 58c98273c8b0..489f1d234285 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -506,10 +506,6 @@ module NodeJSLib { */ abstract class NodeJSClientRequest extends DataFlow::DefaultSourceNode { - /** - * Gets the options object or string URL used to make the request. - */ - abstract DataFlow::Node getOptions(); } /** @@ -536,9 +532,6 @@ module NodeJSLib { result = url } - override DataFlow::Node getOptions() { - result = this.(DataFlow::MethodCallNode).getArgument(0) - } } /** From 2306afdebf828a82e9b65f6b524cd7f77750b20e Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 14:41:19 +0200 Subject: [PATCH 20/50] JS: use extensible architecture for Electron- and NodeClientRequest --- .../javascript/frameworks/ClientRequests.qll | 4 +-- .../semmle/javascript/frameworks/Electron.qll | 34 +++++++++++++++---- .../javascript/frameworks/NodeJSLib.qll | 15 ++++++-- .../ClientRequests/ClientRequest.expected | 6 ++++ .../frameworks/ClientRequests/tst.js | 9 ++++- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index 19bb80d4fd2e..f21be0e710ea 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -10,7 +10,7 @@ import javascript /** * A call that performs a request to a URL. */ -class CustomClientRequest extends DataFlow::CallNode { +class CustomClientRequest extends DataFlow::InvokeNode { /** * Gets the URL of the request. @@ -21,7 +21,7 @@ class CustomClientRequest extends DataFlow::CallNode { /** * A call that performs a request to a URL. */ -class ClientRequest extends DataFlow::CallNode { +class ClientRequest extends DataFlow::InvokeNode { CustomClientRequest custom; diff --git a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll index 13eba4772c57..b7c80eca8031 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll @@ -33,29 +33,51 @@ module Electron { this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() } } - + + /** + * A Node.js-style HTTP or HTTPS request made using an Electron module. + */ + abstract class CustomElectronClientRequest extends NodeJSLib::CustomNodeJSClientRequest {} + /** * A Node.js-style HTTP or HTTPS request made using an Electron module. */ - abstract class ElectronClientRequest extends NodeJSLib::NodeJSClientRequest {} + class ElectronClientRequest extends NodeJSLib::NodeJSClientRequest { + + ElectronClientRequest() { + this instanceof CustomElectronClientRequest + } + + } /** * A Node.js-style HTTP or HTTPS request made using `electron.net`, for example `net.request(url)`. */ - private class NetRequest extends ElectronClientRequest { + private class NetRequest extends CustomElectronClientRequest { NetRequest() { this = DataFlow::moduleMember("electron", "net").getAMemberCall("request") } + + override DataFlow::Node getUrl() { + result = getArgument(0) or + result = getOptionArgument(0, "url") + } + } - - + /** * A Node.js-style HTTP or HTTPS request made using `electron.client`, for example `new client(url)`. */ - private class NewClientRequest extends ElectronClientRequest { + private class NewClientRequest extends CustomElectronClientRequest { NewClientRequest() { this = DataFlow::moduleMember("electron", "ClientRequest").getAnInstantiation() } + + override DataFlow::Node getUrl() { + result = getArgument(0) or + result = getOptionArgument(0, "url") + } + } diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 489f1d234285..ba4dc9731e7f 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -504,14 +504,25 @@ module NodeJSLib { /** * A data flow node that is an HTTP or HTTPS client request made by a Node.js application, for example `http.request(url)`. */ - abstract class NodeJSClientRequest extends DataFlow::DefaultSourceNode { + abstract class CustomNodeJSClientRequest extends CustomClientRequest { + + } + + /** + * A data flow node that is an HTTP or HTTPS client request made by a Node.js application, for example `http.request(url)`. + */ + class NodeJSClientRequest extends ClientRequest { + + NodeJSClientRequest() { + this instanceof CustomNodeJSClientRequest + } } /** * A model of a URL request in the Node.js `http` library. */ - private class NodeHttpUrlRequest extends CustomClientRequest, NodeJSClientRequest { + private class NodeHttpUrlRequest extends CustomNodeJSClientRequest { DataFlow::Node url; diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected index 2b14dea32748..9c12f44e448f 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected @@ -14,3 +14,9 @@ | tst.js:33:5:33:19 | got.stream(url) | tst.js:33:16:33:18 | url | | tst.js:35:5:35:21 | window.fetch(url) | tst.js:35:18:35:20 | url | | tst.js:37:5:37:18 | nodeFetch(url) | tst.js:37:15:37:17 | url | +| tst.js:39:5:39:20 | net.request(url) | tst.js:39:17:39:19 | url | +| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:17:41:28 | { url: url } | +| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:24:41:26 | url | +| tst.js:43:5:43:26 | new Cli ... st(url) | tst.js:43:23:43:25 | url | +| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:23:45:34 | { url: url } | +| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:30:45:32 | url | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js index 3656e30d9660..68a7c2bd5201 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -6,7 +6,7 @@ import express from 'express'; import axios from 'axios'; import got from 'got'; import nodeFetch from 'node-fetch'; - +import {ClientRequest, net} from 'electron'; (function() { request(url); @@ -36,4 +36,11 @@ import nodeFetch from 'node-fetch'; nodeFetch(url); + net.request(url); + + net.request({ url: url }); + + new ClientRequest(url); + + new ClientRequest({ url: url }); }); From 89887e7dc8e6f271bab1ce05b5cd112cb0aaea5e Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 4 Sep 2018 08:21:27 +0200 Subject: [PATCH 21/50] JS: address review comments --- .../ql/src/Security/CWE-918/RequestForgery.qhelp | 2 +- .../semmle/javascript/frameworks/ClientRequests.qll | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp index 75f0b681d168..a540278c67db 100644 --- a/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp @@ -6,7 +6,7 @@

    - Directly incorporating user input into a remote request + Directly incorporating user input into an HTTP request without validating the input can facilitate different kinds of request forgery attacks, where the attacker essentially controls the request. diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index f21be0e710ea..8403a7aec7b2 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -53,7 +53,7 @@ private string urlPropertyName() { } /** - * A model of a URL request in the `request` library. + * A model of a URL request made using the `request` library. */ private class RequestUrlRequest extends CustomClientRequest { @@ -86,7 +86,7 @@ private class RequestUrlRequest extends CustomClientRequest { } /** - * A model of a URL request in the `axios` library. + * A model of a URL request made using the `axios` library. */ private class AxiosUrlRequest extends CustomClientRequest { @@ -103,7 +103,8 @@ private class AxiosUrlRequest extends CustomClientRequest { ) and ( url = getArgument(0) or - url = getOptionArgument([0..2], urlPropertyName()) // slightly over-approximate, in the name of simplicity + // depends on the method name and the call arity, over-approximating slightly in the name of simplicity + url = getOptionArgument([0..2], urlPropertyName()) ) ) } @@ -115,7 +116,7 @@ private class AxiosUrlRequest extends CustomClientRequest { } /** - * A model of a URL request in an implementation of the `fetch` API. + * A model of a URL request made using an implementation of the `fetch` API. */ private class FetchUrlRequest extends CustomClientRequest { @@ -146,7 +147,7 @@ private class FetchUrlRequest extends CustomClientRequest { } /** - * A model of a URL request in the `got` library. + * A model of a URL request made using the `got` library. */ private class GotUrlRequest extends CustomClientRequest { @@ -171,7 +172,7 @@ private class GotUrlRequest extends CustomClientRequest { } /** - * A model of a URL request in the `superagent` library. + * A model of a URL request made using the `superagent` library. */ private class SuperAgentUrlRequest extends CustomClientRequest { From 6e1846b1ca5e24e5fe76acc3993d164e6f6a8c52 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 4 Sep 2018 10:15:56 +0200 Subject: [PATCH 22/50] JS: address doc review comments --- javascript/ql/src/Security/CWE-918/RequestForgery.qhelp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp index a540278c67db..83b541d3a24b 100644 --- a/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.qhelp @@ -40,9 +40,9 @@ The following example shows an HTTP request parameter being used directly in a URL request without validating the input, - which facilitate an SSRF attack. The request - http.get(...) is vulnerable since an attacker can choose - the value of target to be anything he wants. For + which facilitates an SSRF attack. The request + http.get(...) is vulnerable since attackers can choose + the value of target to be anything they want. For instance, the attacker can choose "internal.example.com/#" as the target, causing the URL used in the request to be From f63a3b3f399ad40afc0aee2774986e85ac7d2e99 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 4 Sep 2018 11:44:12 +0200 Subject: [PATCH 23/50] JS: add missing `abstract` modifier --- .../javascript/frameworks/ClientRequests.qll | 2 +- .../ClientRequests/ClientRequest.expected | 40 +++++++++---------- .../ClientRequests/ClientRequest.ql | 2 +- .../ClientRequest_getUrl.expected | 22 ++++++++++ .../ClientRequests/ClientRequest_getUrl.ql | 4 ++ .../frameworks/ClientRequests/tst.js | 4 ++ 6 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected create mode 100644 javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.ql diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index 8403a7aec7b2..a19b5effca35 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -10,7 +10,7 @@ import javascript /** * A call that performs a request to a URL. */ -class CustomClientRequest extends DataFlow::InvokeNode { +abstract class CustomClientRequest extends DataFlow::InvokeNode { /** * Gets the URL of the request. diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected index 9c12f44e448f..ad9bc6a4d196 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.expected @@ -1,22 +1,18 @@ -| tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url | -| tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url | -| tst.js:15:5:15:23 | request.delete(url) | tst.js:15:20:15:22 | url | -| tst.js:17:5:17:25 | request ... url }) | tst.js:17:13:17:24 | { url: url } | -| tst.js:17:5:17:25 | request ... url }) | tst.js:17:20:17:22 | url | -| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:20:19:22 | url | -| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:20:21:22 | url | -| tst.js:23:5:23:17 | http.get(url) | tst.js:23:14:23:16 | url | -| tst.js:25:5:25:14 | axios(url) | tst.js:25:11:25:13 | url | -| tst.js:27:5:27:18 | axios.get(url) | tst.js:27:15:27:17 | url | -| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:11:29:22 | { url: url } | -| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:18:29:20 | url | -| tst.js:31:5:31:12 | got(url) | tst.js:31:9:31:11 | url | -| tst.js:33:5:33:19 | got.stream(url) | tst.js:33:16:33:18 | url | -| tst.js:35:5:35:21 | window.fetch(url) | tst.js:35:18:35:20 | url | -| tst.js:37:5:37:18 | nodeFetch(url) | tst.js:37:15:37:17 | url | -| tst.js:39:5:39:20 | net.request(url) | tst.js:39:17:39:19 | url | -| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:17:41:28 | { url: url } | -| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:24:41:26 | url | -| tst.js:43:5:43:26 | new Cli ... st(url) | tst.js:43:23:43:25 | url | -| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:23:45:34 | { url: url } | -| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:30:45:32 | url | +| tst.js:11:5:11:16 | request(url) | +| tst.js:13:5:13:20 | request.get(url) | +| tst.js:15:5:15:23 | request.delete(url) | +| tst.js:17:5:17:25 | request ... url }) | +| tst.js:19:5:19:23 | requestPromise(url) | +| tst.js:21:5:21:23 | superagent.get(url) | +| tst.js:23:5:23:17 | http.get(url) | +| tst.js:25:5:25:14 | axios(url) | +| tst.js:27:5:27:18 | axios.get(url) | +| tst.js:29:5:29:23 | axios({ url: url }) | +| tst.js:31:5:31:12 | got(url) | +| tst.js:33:5:33:19 | got.stream(url) | +| tst.js:35:5:35:21 | window.fetch(url) | +| tst.js:37:5:37:18 | nodeFetch(url) | +| tst.js:39:5:39:20 | net.request(url) | +| tst.js:41:5:41:29 | net.req ... url }) | +| tst.js:43:5:43:26 | new Cli ... st(url) | +| tst.js:45:5:45:35 | new Cli ... url }) | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql index 00dae0b3b732..0baf011e1df6 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest.ql @@ -1,4 +1,4 @@ import javascript from ClientRequest r -select r, r.getUrl() \ No newline at end of file +select r \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected new file mode 100644 index 000000000000..9c12f44e448f --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.expected @@ -0,0 +1,22 @@ +| tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url | +| tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url | +| tst.js:15:5:15:23 | request.delete(url) | tst.js:15:20:15:22 | url | +| tst.js:17:5:17:25 | request ... url }) | tst.js:17:13:17:24 | { url: url } | +| tst.js:17:5:17:25 | request ... url }) | tst.js:17:20:17:22 | url | +| tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:20:19:22 | url | +| tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:20:21:22 | url | +| tst.js:23:5:23:17 | http.get(url) | tst.js:23:14:23:16 | url | +| tst.js:25:5:25:14 | axios(url) | tst.js:25:11:25:13 | url | +| tst.js:27:5:27:18 | axios.get(url) | tst.js:27:15:27:17 | url | +| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:11:29:22 | { url: url } | +| tst.js:29:5:29:23 | axios({ url: url }) | tst.js:29:18:29:20 | url | +| tst.js:31:5:31:12 | got(url) | tst.js:31:9:31:11 | url | +| tst.js:33:5:33:19 | got.stream(url) | tst.js:33:16:33:18 | url | +| tst.js:35:5:35:21 | window.fetch(url) | tst.js:35:18:35:20 | url | +| tst.js:37:5:37:18 | nodeFetch(url) | tst.js:37:15:37:17 | url | +| tst.js:39:5:39:20 | net.request(url) | tst.js:39:17:39:19 | url | +| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:17:41:28 | { url: url } | +| tst.js:41:5:41:29 | net.req ... url }) | tst.js:41:24:41:26 | url | +| tst.js:43:5:43:26 | new Cli ... st(url) | tst.js:43:23:43:25 | url | +| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:23:45:34 | { url: url } | +| tst.js:45:5:45:35 | new Cli ... url }) | tst.js:45:30:45:32 | url | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.ql b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.ql new file mode 100644 index 000000000000..00dae0b3b732 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequest_getUrl.ql @@ -0,0 +1,4 @@ +import javascript + +from ClientRequest r +select r, r.getUrl() \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js index 68a7c2bd5201..025de83861f1 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -43,4 +43,8 @@ import {ClientRequest, net} from 'electron'; new ClientRequest(url); new ClientRequest({ url: url }); + + unknown(url); + + unknown({ url:url }); }); From 42faabc552f7d5c79ee9cf664f78037ee8e1137a Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 31 Aug 2018 15:32:43 +0200 Subject: [PATCH 24/50] C#: Rename and restructure control flow graph entities Follow a naming structure similar to the data flow library: - `ControlFlowNode` -> `ControlFlow::Node`. - `CallableEntryNode` -> `ControlFlow::Nodes::EntryNode`. - `CallableExitNode` -> `ControlFlow::Nodes::ExitNode`. - `ControlFlowEdgeType` -> `ControlFlow::SuccessorType`. - `ControlFlowEdgeSuccessor` -> `ControlFlow::SuccessorTypes::NormalSuccessor`. - `ControlFlowEdgeConditional -> ControlFlow::SuccessorTypes::ConditionalSuccessor`. - `ControlFlowEdgeBoolean` -> `ControlFlow::SuccessorTypes::BooleanSuccessor`. - `ControlFlowEdgeNullness` -> `ControlFlow::SuccessorTypes::NullnessSuccessor`. - `ControlFlowEdgeMatching` -> `ControlFlow::SuccessorTypes::MatchingSuccessor`. - `ControlFlowEdgeEmptiness` -> `ControlFlow::SuccessorTypes::EmptinessSuccessor`. - `ControlFlowEdgeReturn` -> `ControlFlow::SuccessorTypes::ReturnSuccessor`. - `ControlFlowEdgeBreak` -> `ControlFlow::SuccessorTypes::BreakSuccessor`. - `ControlFlowEdgeContinue` -> `ControlFlow::SuccessorTypes::ContinueSuccessor`. - `ControlFlowEdgeGotoLabel` -> `ControlFlow::SuccessorTypes::GotoLabelSuccessor`. - `ControlFlowEdgeGotoCase` -> `ControlFlow::SuccessorTypes::GotoCaseSuccessor`. - `ControlFlowEdgeGotoDefault` -> `ControlFlow::SuccessorTypes::GotoDefaultSuccessor`. - `ControlFlowEdgeException` -> `ControlFlow::SuccessorTypes::ExceptionSuccessor` --- csharp/ql/src/API Abuse/Dispose.qll | 6 +- .../Control-Flow/ConstantCondition.ql | 13 +- .../Bad Practices/ErroneousClassCompare.ql | 2 +- csharp/ql/src/Concurrency/Concurrency.qll | 12 +- .../Concurrency/UnsynchronizedStaticAccess.ql | 5 +- .../Likely Bugs/NestedLoopsSameVariable.ql | 5 +- .../src/Likely Bugs/Statements/UseBraces.ql | 2 +- .../src/Likely Bugs/UncheckedCastInEquals.ql | 4 +- .../ql/src/Performance/StringBuilderInLoop.ql | 2 +- .../CWE-384/AbandonSession.ql | 11 +- csharp/ql/src/csharp.qll | 9 +- csharp/ql/src/external/ExternalArtifact.qll | 4 +- .../ql/src/semmle/code/csharp/Assignable.qll | 37 +- csharp/ql/src/semmle/code/csharp/Callable.qll | 4 +- csharp/ql/src/semmle/code/csharp/Event.qll | 3 +- .../semmle/code/csharp/commons/Assertions.qll | 10 +- .../code/csharp/commons/ConsistencyChecks.qll | 10 +- .../semmle/code/csharp/commons/Constants.qll | 7 +- .../code/csharp/controlflow/BasicBlocks.qll | 22 +- .../csharp/controlflow/ControlFlowElement.qll | 18 +- .../csharp/controlflow/ControlFlowGraph.qll | 5632 +++++++++-------- .../semmle/code/csharp/controlflow/Guards.qll | 2 +- .../semmle/code/csharp/dataflow/Nullness.qll | 14 +- .../src/semmle/code/csharp/dataflow/SSA.qll | 29 +- .../code/csharp/dataflow/internal/BaseSSA.qll | 2 +- .../code/csharp/exprs/ArithmeticOperation.qll | 2 +- .../ql/src/semmle/code/csharp/exprs/Call.qll | 10 +- .../semmle/code/csharp/frameworks/Format.qll | 4 +- .../code/csharp/frameworks/system/Web.qll | 2 +- .../system/runtime/InteropServices.qll | 2 +- .../dataflow/MissingXMLValidation.qll | 2 +- .../security/dataflow/XMLEntityInjection.qll | 2 +- .../code/csharp/security/dataflow/XSS.qll | 2 +- .../controlflow/graph/ConditionBlock.ql | 3 +- .../controlflow/graph/ConditionalFlow.ql | 5 +- .../controlflow/graph/Dominance.ql | 3 +- .../controlflow/graph/ElementGraph.ql | 3 +- .../controlflow/graph/EntryElement.ql | 2 +- .../controlflow/graph/ExitElement.ql | 2 +- .../controlflow/graph/FinallyNode.ql | 7 +- .../controlflow/graph/NodeGraph.ql | 5 +- .../ql/test/library-tests/csharp7/IsFlow.ql | 5 +- .../dataflow/defuse/defUseEquivalence.ql | 5 +- .../defuse/parameterUseEquivalence.ql | 5 +- .../dataflow/defuse/useUseEquivalence.ql | 7 +- .../dataflow/ssa-large/countssa.ql | 2 +- csharp/ql/test/library-tests/goto/Goto1.ql | 5 +- 47 files changed, 3010 insertions(+), 2940 deletions(-) diff --git a/csharp/ql/src/API Abuse/Dispose.qll b/csharp/ql/src/API Abuse/Dispose.qll index 556e81e3c913..508b8b35f19b 100644 --- a/csharp/ql/src/API Abuse/Dispose.qll +++ b/csharp/ql/src/API Abuse/Dispose.qll @@ -5,7 +5,7 @@ private import semmle.code.csharp.frameworks.system.web.UI class DisposableType extends RefType { DisposableType() { - this.getABaseType+() = getSystemIDisposableInterface() + this.getABaseType+() instanceof SystemIDisposableInterface } } @@ -17,13 +17,13 @@ class DisposableField extends Field { class WebControl extends RefType { WebControl() { - this.getBaseClass*() = getSystemWebUIControlClass() + this.getBaseClass*() instanceof SystemWebUIControlClass } } class WebPage extends RefType { WebPage() { - this.getBaseClass*() = getSystemWebUIPageClass() + this.getBaseClass*() instanceof SystemWebUIPageClass } } diff --git a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql index b9cd9af74f99..db38047c4e2d 100644 --- a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql +++ b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql @@ -14,7 +14,6 @@ import csharp import semmle.code.csharp.commons.Assertions import semmle.code.csharp.commons.Constants -import ControlFlowGraph /** A constant condition. */ abstract class ConstantCondition extends Expr { @@ -76,13 +75,13 @@ class ConstantNullnessCondition extends ConstantCondition { boolean b; ConstantNullnessCondition() { - forex(ControlFlowNode cfn | + forex(ControlFlow::Node cfn | cfn = this.getAControlFlowNode() | - exists(ControlFlowEdgeNullness t | + exists(ControlFlow::SuccessorTypes::NullnessSuccessor t | exists(cfn.getASuccessorByType(t)) | if t.isNull() then b = true else b = false ) and - strictcount(ControlFlowEdgeType t | exists(cfn.getASuccessorByType(t))) = 1 + strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1 ) } @@ -99,13 +98,13 @@ class ConstantMatchingCondition extends ConstantCondition { boolean b; ConstantMatchingCondition() { - forex(ControlFlowNode cfn | + forex(ControlFlow::Node cfn | cfn = this.getAControlFlowNode() | - exists(ControlFlowEdgeMatching t | + exists(ControlFlow::SuccessorTypes::MatchingSuccessor t | exists(cfn.getASuccessorByType(t)) | if t.isMatch() then b = true else b = false ) and - strictcount(ControlFlowEdgeType t | exists(cfn.getASuccessorByType(t))) = 1 + strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1 ) } diff --git a/csharp/ql/src/Bad Practices/ErroneousClassCompare.ql b/csharp/ql/src/Bad Practices/ErroneousClassCompare.ql index 8ab675280f64..628547d34bcd 100644 --- a/csharp/ql/src/Bad Practices/ErroneousClassCompare.ql +++ b/csharp/ql/src/Bad Practices/ErroneousClassCompare.ql @@ -35,5 +35,5 @@ class StringComparison extends Expr { from StringComparison sc, PropertyAccess pa where sc.getAnOperand() instanceof StringLiteral and sc.getAnOperand() = pa - and pa.getTarget() = getSystemTypeClass().getFullNameProperty() + and pa.getTarget() = any(SystemTypeClass c).getFullNameProperty() select sc, "Erroneous class compare." diff --git a/csharp/ql/src/Concurrency/Concurrency.qll b/csharp/ql/src/Concurrency/Concurrency.qll index 998cac90217e..232145a2cac6 100644 --- a/csharp/ql/src/Concurrency/Concurrency.qll +++ b/csharp/ql/src/Concurrency/Concurrency.qll @@ -102,17 +102,17 @@ class LockStmtBlock extends LockedBlock exists( LockStmt s | this=s.getBlock() ) } - predicate isLockThis() + override predicate isLockThis() { exists( LockStmt s | this=s.getBlock() and s.isLockThis() ) } - Variable getLockVariable() + override Variable getLockVariable() { exists( LockStmt s | this=s.getBlock() and result=s.getLockVariable() ) } - Type getLockTypeObject() + override Type getLockTypeObject() { exists( LockStmt s | this=s.getBlock() and result=s.getLockTypeObject() ) } @@ -138,17 +138,17 @@ class SynchronizedMethodBlock extends LockedBlock { exists( SynchronizedMethod m | this=m.getStatementBody() ) } - predicate isLockThis() + override predicate isLockThis() { exists( SynchronizedMethod m | this=m.getStatementBody() and m.isLockThis() ) } - Variable getLockVariable() + override Variable getLockVariable() { none() } - Type getLockTypeObject() + override Type getLockTypeObject() { exists( SynchronizedMethod m | this=m.getStatementBody() and result=m.getLockTypeObject() ) } diff --git a/csharp/ql/src/Concurrency/UnsynchronizedStaticAccess.ql b/csharp/ql/src/Concurrency/UnsynchronizedStaticAccess.ql index 78cb709dab63..724bad88c645 100644 --- a/csharp/ql/src/Concurrency/UnsynchronizedStaticAccess.ql +++ b/csharp/ql/src/Concurrency/UnsynchronizedStaticAccess.ql @@ -13,7 +13,6 @@ import csharp import DataMembers import ThreadCreation -import ControlFlowGraph predicate correctlySynchronized(CollectionMember c, Expr access) { access = c.getAReadOrWrite() and @@ -24,9 +23,9 @@ predicate correctlySynchronized(CollectionMember c, Expr access) { ) } -ControlFlowNode unlockedReachable(Callable a) { +ControlFlow::Node unlockedReachable(Callable a) { result = a.getEntryPoint() or - exists(ControlFlowNode mid | mid = unlockedReachable(a) | + exists(ControlFlow::Node mid | mid = unlockedReachable(a) | not mid.getElement() instanceof LockingCall and result = mid.getASuccessor() ) diff --git a/csharp/ql/src/Likely Bugs/NestedLoopsSameVariable.ql b/csharp/ql/src/Likely Bugs/NestedLoopsSameVariable.ql index e23770cb2668..a7fce8d21574 100644 --- a/csharp/ql/src/Likely Bugs/NestedLoopsSameVariable.ql +++ b/csharp/ql/src/Likely Bugs/NestedLoopsSameVariable.ql @@ -13,7 +13,6 @@ import csharp import semmle.code.csharp.commons.ComparisonTest -import semmle.code.csharp.controlflow.ControlFlowGraph::ControlFlowGraph import semmle.code.csharp.commons.StructuralComparison as SC /** A structural comparison configuration for comparing the conditions of nested `for` loops. */ @@ -87,13 +86,13 @@ class NestedForLoopSameVariable extends ForStmt { } /** Finds elements inside the outer loop that are no longer guarded by the loop invariant. */ - private ControlFlowNode getAnUnguardedNode() + private ControlFlow::Node getAnUnguardedNode() { result.getElement().getParent+() = getOuterForStmt().getBody() and ( result = this.getCondition().(ControlFlowElement).getAControlFlowExitNode().getAFalseSuccessor() or - exists(ControlFlowNode mid | mid = getAnUnguardedNode() | + exists(ControlFlow::Node mid | mid = getAnUnguardedNode() | mid.getASuccessor() = result and not exists(getAComparisonTest(result.getElement())) ) diff --git a/csharp/ql/src/Likely Bugs/Statements/UseBraces.ql b/csharp/ql/src/Likely Bugs/Statements/UseBraces.ql index fd3348c7dcde..b2b2b29333c5 100755 --- a/csharp/ql/src/Likely Bugs/Statements/UseBraces.ql +++ b/csharp/ql/src/Likely Bugs/Statements/UseBraces.ql @@ -14,7 +14,7 @@ import csharp // Iterate the control flow until we reach a Stmt -Stmt findSuccessorStmt(ControlFlowGraph::ControlFlowNode n) +Stmt findSuccessorStmt(ControlFlow::Node n) { result=n.getElement() or not n.getElement() instanceof Stmt and result=findSuccessorStmt(n.getASuccessor()) diff --git a/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql b/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql index 5ea9e1a7792c..5732cd764079 100644 --- a/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql +++ b/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql @@ -12,11 +12,11 @@ import csharp import semmle.code.csharp.frameworks.System -predicate nodeBeforeParameterAccess(ControlFlowGraph::ControlFlowNode node) +predicate nodeBeforeParameterAccess(ControlFlow::Node node) { exists(EqualsMethod equals | equals.getBody() = node.getElement()) or - exists(EqualsMethod equals, Parameter param, ControlFlowGraph::ControlFlowNode mid | + exists(EqualsMethod equals, Parameter param, ControlFlow::Node mid | equals.getParameter(0) = param and equals.getAChild*() = mid.getElement() and nodeBeforeParameterAccess(mid) and diff --git a/csharp/ql/src/Performance/StringBuilderInLoop.ql b/csharp/ql/src/Performance/StringBuilderInLoop.ql index 517aed6378f7..33a7b07baac5 100644 --- a/csharp/ql/src/Performance/StringBuilderInLoop.ql +++ b/csharp/ql/src/Performance/StringBuilderInLoop.ql @@ -11,7 +11,7 @@ import csharp import semmle.code.csharp.frameworks.system.Text -from ObjectCreation creation, LoopStmt loop, ControlFlowGraph::ControlFlowNode loopEntryNode +from ObjectCreation creation, LoopStmt loop, ControlFlow::Node loopEntryNode where creation.getType() instanceof SystemTextStringBuilderClass and loopEntryNode = loop.getBody().getAControlFlowEntryNode() and loop.getBody().getAChild*() = creation diff --git a/csharp/ql/src/Security Features/CWE-384/AbandonSession.ql b/csharp/ql/src/Security Features/CWE-384/AbandonSession.ql index 91fcc6402742..d766f926c1ae 100644 --- a/csharp/ql/src/Security Features/CWE-384/AbandonSession.ql +++ b/csharp/ql/src/Security Features/CWE-384/AbandonSession.ql @@ -12,10 +12,9 @@ */ import csharp -import ControlFlowGraph import semmle.code.csharp.frameworks.system.web.Security -predicate loginMethod(Method m, ControlFlowEdgeType flowFrom) +predicate loginMethod(Method m, ControlFlow::SuccessorType flowFrom) { ( m = any(SystemWebSecurityMembershipClass c).getValidateUserMethod() @@ -23,11 +22,11 @@ predicate loginMethod(Method m, ControlFlowEdgeType flowFrom) m = any(SystemWebSecurityFormsAuthenticationClass c).getAuthenticateMethod() ) and - flowFrom.(ControlFlowEdgeBoolean).getValue()=true + flowFrom.(ControlFlow::SuccessorTypes::BooleanSuccessor).getValue()=true or m = any(SystemWebSecurityFormsAuthenticationClass c).getSignOutMethod() and - flowFrom instanceof ControlFlowEdgeSuccessor + flowFrom instanceof ControlFlow::SuccessorTypes::NormalSuccessor } /** The `System.Web.SessionState.HttpSessionState` class. */ @@ -62,14 +61,14 @@ predicate sessionUse(MemberAccess ma) } /** A control flow step that is not sanitised by a call to clear the session. */ -predicate controlStep(ControlFlowNode s1, ControlFlowNode s2) +predicate controlStep(ControlFlow::Node s1, ControlFlow::Node s2) { s2 = s1.getASuccessor() and not sessionEndMethod(s2.getElement().(MethodCall).getTarget()) } -from ControlFlowNode loginCall, Method loginMethod, ControlFlowNode sessionUse, ControlFlowEdgeType fromLoginFlow +from ControlFlow::Node loginCall, Method loginMethod, ControlFlow::Node sessionUse, ControlFlow::SuccessorType fromLoginFlow where loginMethod = loginCall.getElement().(MethodCall).getTarget() and loginMethod(loginMethod, fromLoginFlow) and sessionUse(sessionUse.getElement()) diff --git a/csharp/ql/src/csharp.qll b/csharp/ql/src/csharp.qll index c2a26afb2613..5a3cc0467cad 100644 --- a/csharp/ql/src/csharp.qll +++ b/csharp/ql/src/csharp.qll @@ -29,11 +29,18 @@ import semmle.code.csharp.exprs.Dynamic import semmle.code.csharp.exprs.Expr import semmle.code.csharp.exprs.Literal import semmle.code.csharp.exprs.LogicalOperation -import semmle.code.csharp.controlflow.ControlFlowGraph as ControlFlowGraph +import semmle.code.csharp.controlflow.ControlFlowGraph import semmle.code.csharp.dataflow.DataFlow import semmle.code.csharp.dataflow.TaintTracking import semmle.code.csharp.dataflow.SSA +/** DEPRECATED: Use `ControlFlow` instead. */ +deprecated +module ControlFlowGraph { + import semmle.code.csharp.controlflow.ControlFlowGraph + import ControlFlow +} + /** Whether the source was extracted without a build command. */ predicate extractionIsStandalone() { exists(SourceFile f | f.extractedStandalone()) diff --git a/csharp/ql/src/external/ExternalArtifact.qll b/csharp/ql/src/external/ExternalArtifact.qll index 1ce848229f93..d426a8f31c7e 100644 --- a/csharp/ql/src/external/ExternalArtifact.qll +++ b/csharp/ql/src/external/ExternalArtifact.qll @@ -9,7 +9,7 @@ class ExternalDefect extends @externalDefect, Element { float getSeverity() { externalDefects(this, _, _, _, result) } - Location getLocation() { externalDefects(this,_,result,_,_) } + override Location getLocation() { externalDefects(this,_,result,_,_) } override string toString() { result = getQueryPath() + ": " + getLocation() + " - " + getMessage() @@ -22,7 +22,7 @@ class ExternalMetric extends @externalMetric, Element { float getValue() { externalMetrics(this, _, _, result) } - Location getLocation() { externalMetrics(this,_,result,_) } + override Location getLocation() { externalMetrics(this,_,result,_) } override string toString() { result = getQueryPath() + ": " + getLocation() + " - " + getValue() diff --git a/csharp/ql/src/semmle/code/csharp/Assignable.qll b/csharp/ql/src/semmle/code/csharp/Assignable.qll index 2b1c8f7cd196..742c54ac2aee 100644 --- a/csharp/ql/src/semmle/code/csharp/Assignable.qll +++ b/csharp/ql/src/semmle/code/csharp/Assignable.qll @@ -3,7 +3,6 @@ */ import csharp -private import ControlFlowGraph /** * An assignable, that is, an element that can be assigned to. Either a @@ -348,7 +347,7 @@ private cached module AssignableDefinitionImpl { cached predicate isUncertainRefCall(Call c, AssignableAccess aa) { isRelevantRefCall(c, aa) and - exists(ControlFlowGraph::BasicBlock bb, Parameter p | + exists(ControlFlow::BasicBlock bb, Parameter p | isAnalyzableRefCall(c, aa, p) | parameterReachesWithoutDef(p, bb) and bb.getLastNode() = p.getCallable().getExitPoint() @@ -360,14 +359,14 @@ private cached module AssignableDefinitionImpl { * entry point of `p`'s callable to basic block `bb` without passing through * any assignments to `p`. */ - private predicate parameterReachesWithoutDef(Parameter p, BasicBlock bb) { + private predicate parameterReachesWithoutDef(Parameter p, ControlFlow::BasicBlock bb) { not basicBlockRefParamDef(bb, p) and ( isAnalyzableRefCall(_, _, p) and p.getCallable().getEntryPoint() = bb.getFirstNode() or - exists(BasicBlock mid | + exists(ControlFlow::BasicBlock mid | parameterReachesWithoutDef(p, mid) | bb = mid.getASuccessor() ) @@ -375,7 +374,7 @@ private cached module AssignableDefinitionImpl { } /** Holds if a node in basic block `bb` assigns to `ref` parameter `p`. */ - private predicate basicBlockRefParamDef(BasicBlock bb, Parameter p) { + private predicate basicBlockRefParamDef(ControlFlow::BasicBlock bb, Parameter p) { bb.getANode() = getAnAnalyzableRefDef(_, _, p).getAControlFlowNode() } @@ -447,11 +446,11 @@ class AssignableDefinition extends TAssignableDefinition { * the definitions of `x` and `y` in `M(out x, out y)` and `(x, y) = (0, 1)` * relate to the same call to `M` and assignment node, respectively. */ - ControlFlowNode getAControlFlowNode() { none() } + ControlFlow::Node getAControlFlowNode() { none() } /** DEPRECATED: Use `getAControlFlowNode()` instead. */ deprecated - ControlFlowNode getControlFlowNode() { result = this.getAControlFlowNode() } + ControlFlow::Node getControlFlowNode() { result = this.getAControlFlowNode() } /** Gets the enclosing callable of this definition. */ Callable getEnclosingCallable() { @@ -603,7 +602,7 @@ module AssignableDefinitions { result = a } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = a.getAControlFlowNode() } @@ -644,7 +643,7 @@ module AssignableDefinitions { * orders of the definitions of `x`, `y`, and `z` are 0, 1, and 2, respectively. */ int getEvaluationOrder() { - exists(BasicBlock bb, int i | + exists(ControlFlow::BasicBlock bb, int i | bb.getNode(i).getElement() = leaf | i = rank[result + 1](int j, TupleAssignmentDefinition def | bb.getNode(j).getElement() = def.getLeaf() and @@ -655,7 +654,7 @@ module AssignableDefinitions { ) } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = ae.getAControlFlowNode() } @@ -690,7 +689,7 @@ module AssignableDefinitions { * the definitions of `x` and `y` are 0 and 1, respectively. */ int getIndex() { - exists(BasicBlock bb, int i | + exists(ControlFlow::BasicBlock bb, int i | bb.getNode(i).getElement() = aa | i = rank[result + 1](int j, OutRefDefinition def | bb.getNode(j).getElement() = def.getTargetAccess() and @@ -701,7 +700,7 @@ module AssignableDefinitions { ) } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = this.getCall().getAControlFlowNode() } @@ -733,7 +732,7 @@ module AssignableDefinitions { result = mo } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = mo.getAControlFlowNode() } @@ -757,7 +756,7 @@ module AssignableDefinitions { result = lvde } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = lvde.getAControlFlowNode() } @@ -782,7 +781,7 @@ module AssignableDefinitions { result = p } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = p.getCallable().getEntryPoint() } @@ -810,7 +809,7 @@ module AssignableDefinitions { result = aoe } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = aoe.getAControlFlowNode() } @@ -834,7 +833,7 @@ module AssignableDefinitions { result = ipe.getVariableDeclExpr() } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = this.getDeclaration().getAControlFlowNode() } @@ -871,7 +870,7 @@ module AssignableDefinitions { result = tc.getVariableDeclExpr() } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { result = this.getDeclaration().getAControlFlowNode() } @@ -907,7 +906,7 @@ module AssignableDefinitions { result = a } - override ControlFlowNode getAControlFlowNode() { + override ControlFlow::Node getAControlFlowNode() { none() // initializers are currently not part of the CFG } diff --git a/csharp/ql/src/semmle/code/csharp/Callable.qll b/csharp/ql/src/semmle/code/csharp/Callable.qll index 458bd3c53ff5..29d7c843565f 100644 --- a/csharp/ql/src/semmle/code/csharp/Callable.qll +++ b/csharp/ql/src/semmle/code/csharp/Callable.qll @@ -167,12 +167,12 @@ class Callable extends DotNet::Callable, Parameterizable, ExprOrStmtParent, @cal final predicate hasExpressionBody() { exists(getExpressionBody()) } /** Gets the entry point in the control graph for this callable. */ - ControlFlowGraph::CallableEntryNode getEntryPoint() { + ControlFlow::Nodes::EntryNode getEntryPoint() { result.getCallable() = this } /** Gets the exit point in the control graph for this callable. */ - ControlFlowGraph::CallableExitNode getExitPoint() { + ControlFlow::Nodes::ExitNode getExitPoint() { result.getCallable() = this } diff --git a/csharp/ql/src/semmle/code/csharp/Event.qll b/csharp/ql/src/semmle/code/csharp/Event.qll index c6fec8eac34f..459b6732c4f6 100644 --- a/csharp/ql/src/semmle/code/csharp/Event.qll +++ b/csharp/ql/src/semmle/code/csharp/Event.qll @@ -92,8 +92,7 @@ class EventAccessor extends Accessor, @event_accessor { result instanceof VoidType } - /** Gets the assembly name of this event accessor. */ - string getAssemblyName() { event_accessors(this,_,result,_,_) } + override string getAssemblyName() { event_accessors(this,_,result,_,_) } override EventAccessor getSourceDeclaration() { event_accessors(this,_,_,_,result) } diff --git a/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll b/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll index 4e85a7f1356d..f9e94429e55a 100644 --- a/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll +++ b/csharp/ql/src/semmle/code/csharp/commons/Assertions.qll @@ -29,7 +29,7 @@ abstract class AssertNonNullMethod extends AssertMethod { */ class SystemDiagnosticsDebugAssertTrueMethod extends AssertTrueMethod { SystemDiagnosticsDebugAssertTrueMethod() { - this = getSystemDiagnosticsDebugClass().getAssertMethod() + this = any(SystemDiagnosticsDebugClass c).getAssertMethod() } override int getAssertionIndex() { result = 0 } @@ -38,7 +38,7 @@ class SystemDiagnosticsDebugAssertTrueMethod extends AssertTrueMethod { /** A Visual Studio assertion method. */ class VSTestAssertTrueMethod extends AssertTrueMethod { VSTestAssertTrueMethod() { - this = getVSTestAssertClass().getIsTrueMethod() + this = any(VSTestAssertClass c).getIsTrueMethod() } override int getAssertionIndex() { result = 0 } @@ -47,7 +47,7 @@ class VSTestAssertTrueMethod extends AssertTrueMethod { /** A Visual Studio negated assertion method. */ class VSTestAssertFalseMethod extends AssertFalseMethod { VSTestAssertFalseMethod() { - this = getVSTestAssertClass().getIsFalseMethod() + this = any(VSTestAssertClass c).getIsFalseMethod() } override int getAssertionIndex() { result = 0 } @@ -56,7 +56,7 @@ class VSTestAssertFalseMethod extends AssertFalseMethod { /** A Visual Studio `null` assertion method. */ class VSTestAssertNullMethod extends AssertNullMethod { VSTestAssertNullMethod() { - this = getVSTestAssertClass().getIsNullMethod() + this = any(VSTestAssertClass c).getIsNullMethod() } override int getAssertionIndex() { result = 0 } @@ -65,7 +65,7 @@ class VSTestAssertNullMethod extends AssertNullMethod { /** A Visual Studio non-`null` assertion method. */ class VSTestAssertNonNullMethod extends AssertNonNullMethod { VSTestAssertNonNullMethod() { - this = getVSTestAssertClass().getIsNotNullMethod() + this = any(VSTestAssertClass c).getIsNotNullMethod() } override int getAssertionIndex() { result = 0 } diff --git a/csharp/ql/src/semmle/code/csharp/commons/ConsistencyChecks.qll b/csharp/ql/src/semmle/code/csharp/commons/ConsistencyChecks.qll index 3af518d35289..226ddfe621db 100644 --- a/csharp/ql/src/semmle/code/csharp/commons/ConsistencyChecks.qll +++ b/csharp/ql/src/semmle/code/csharp/commons/ConsistencyChecks.qll @@ -40,7 +40,7 @@ module SsaChecks { import Ssa predicate nonUniqueSsaDef(AssignableRead read, string m) { - exists(ControlFlowNode cfn | + exists(ControlFlow::Node cfn | strictcount(Definition def | def.getAReadAtNode(cfn) = read) > 1 ) and @@ -48,7 +48,7 @@ module SsaChecks { } predicate notDominatedByDef(AssignableRead read, string m) { - exists(Definition def, BasicBlock bb, ControlFlowNode rnode, ControlFlowNode dnode, int i | + exists(Definition def, BasicBlock bb, ControlFlow::Node rnode, ControlFlow::Node dnode, int i | def.getAReadAtNode(rnode) = read | def.definesAt(bb, i) and dnode = bb.getNode(max(int j | j = i or j = 0)) and @@ -82,12 +82,10 @@ module SsaChecks { } module CfgChecks { - import ControlFlowGraph - predicate multipleSuccessors(ControlFlowElement cfe, string m) { - exists(ControlFlowNode cfn | + exists(ControlFlow::Node cfn | cfn = cfe.getAControlFlowNode() | - strictcount(cfn.getASuccessorByType(any(ControlFlowEdgeSuccessor e))) > 1 and + strictcount(cfn.getASuccessorByType(any(ControlFlow::SuccessorTypes::NormalSuccessor e))) > 1 and m = "Multiple (non-conditional/exceptional) successors" ) } diff --git a/csharp/ql/src/semmle/code/csharp/commons/Constants.qll b/csharp/ql/src/semmle/code/csharp/commons/Constants.qll index bc8642f461aa..0b56389473f4 100644 --- a/csharp/ql/src/semmle/code/csharp/commons/Constants.qll +++ b/csharp/ql/src/semmle/code/csharp/commons/Constants.qll @@ -1,6 +1,5 @@ /** Provides logic for determining constant expressions. */ import csharp -private import ControlFlowGraph private import semmle.code.csharp.commons.ComparisonTest private import semmle.code.csharp.commons.StructuralComparison as StructuralComparison @@ -8,10 +7,10 @@ private import semmle.code.csharp.commons.StructuralComparison as StructuralComp * Holds if `e` is a condition that always evaluates to Boolean value `b`. */ predicate isConstantCondition(Expr e, boolean b) { - forex(ControlFlowNode cfn | + forex(ControlFlow::Node cfn | cfn = e.getAControlFlowNode() | - exists(cfn.getASuccessorByType(any(ControlFlowEdgeBoolean t | t.getValue() = b))) and - strictcount(ControlFlowEdgeType t | exists(cfn.getASuccessorByType(t))) = 1 + exists(cfn.getASuccessorByType(any(ControlFlow::SuccessorTypes::BooleanSuccessor t | t.getValue() = b))) and + strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1 ) } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll index e28eb8dfe94f..2bacb951da57 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll @@ -99,20 +99,20 @@ class BasicBlock extends TBasicBlockStart { } /** Gets the control flow node at a specific (zero-indexed) position in this basic block. */ - ControlFlowGraph::ControlFlowNode getNode(int pos) { bbIndex(getFirstNode(), result, pos) } + ControlFlow::Node getNode(int pos) { bbIndex(getFirstNode(), result, pos) } /** Gets a control flow node in this basic block. */ - ControlFlowGraph::ControlFlowNode getANode() { + ControlFlow::Node getANode() { result = getNode(_) } /** Gets the first control flow node in this basic block. */ - ControlFlowGraph::ControlFlowNode getFirstNode() { + ControlFlow::Node getFirstNode() { this = TBasicBlockStart(result) } /** Gets the last control flow node in this basic block. */ - ControlFlowGraph::ControlFlowNode getLastNode() { + ControlFlow::Node getLastNode() { result = getNode(length() - 1) } @@ -326,22 +326,22 @@ class BasicBlock extends TBasicBlockStart { private cached module Internal { /** Internal representation of basic blocks. */ cached newtype TBasicBlock = - TBasicBlockStart(ControlFlowGraph::ControlFlowNode cfn) { startsBB(cfn) } + TBasicBlockStart(ControlFlow::Node cfn) { startsBB(cfn) } /** Holds if `cfn` starts a new basic block. */ - private predicate startsBB(ControlFlowGraph::ControlFlowNode cfn) { + private predicate startsBB(ControlFlow::Node cfn) { not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor()) or cfn.isJoin() or - exists(ControlFlowGraph::ControlFlowNode pred | pred = cfn.getAPredecessor() | strictcount(pred.getASuccessor()) > 1) + exists(ControlFlow::Node pred | pred = cfn.getAPredecessor() | strictcount(pred.getASuccessor()) > 1) } /** * Holds if `succ` is a control flow successor of `pred` within * the same basic block. */ - private predicate intraBBSucc(ControlFlowGraph::ControlFlowNode pred, ControlFlowGraph::ControlFlowNode succ) { + private predicate intraBBSucc(ControlFlow::Node pred, ControlFlow::Node succ) { succ = pred.getASuccessor() and not startsBB(succ) } @@ -353,7 +353,7 @@ private cached module Internal { * that starts a basic block to `cfn` along the `intraBBSucc` relation. */ cached - predicate bbIndex(ControlFlowGraph::ControlFlowNode bbStart, ControlFlowGraph::ControlFlowNode cfn, int i) = + predicate bbIndex(ControlFlow::Node bbStart, ControlFlow::Node cfn, int i) = shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i) /** @@ -395,7 +395,7 @@ class EntryBasicBlock extends BasicBlock { /** Holds if `bb` is an entry basic block. */ private predicate entryBB(BasicBlock bb) { - bb.getFirstNode() instanceof ControlFlowGraph::CallableEntryNode + bb.getFirstNode() instanceof ControlFlow::Nodes::EntryNode } /** @@ -408,7 +408,7 @@ class ExitBasicBlock extends BasicBlock { /** Holds if `bb` is an exit basic block. */ private predicate exitBB(BasicBlock bb) { - bb.getLastNode() instanceof ControlFlowGraph::CallableExitNode + bb.getLastNode() instanceof ControlFlow::Nodes::ExitNode } /** diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll index 752b6d86d0ce..aa8f1eca78a2 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll @@ -7,12 +7,10 @@ private import semmle.code.csharp.ExprOrStmtParent * A program element that can possess control flow. That is, either a statement or * an expression. * - * A control flow element can be mapped to a control flow node (`ControlFlowNode`) + * A control flow element can be mapped to a control flow node (`ControlFlow::Node`) * via `getAControlFlowNode()`. There is a one-to-many relationship between - * `ControlFlowElement`s and `ControlFlowNode`s. This allows control flow + * control flow elements and control flow nodes. This allows control flow * splitting, for example modeling the control flow through `finally` blocks. - * - * `ControlFlowNode`s are nodes in the control flow graph. */ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element { /** Gets the enclosing callable of this element, if any. */ @@ -22,26 +20,26 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element { * Gets a control flow node for this element. That is, a node in the * control flow graph that corresponds to this element. * - * Typically, there is exactly one `ControlFlowNode` associated with a + * Typically, there is exactly one `ControlFlow::Node` associated with a * `ControlFlowElement`, but a `ControlFlowElement` may be split into - * several `ControlFlowNode`s, for example to represent the continuation + * several `ControlFlow::Node`s, for example to represent the continuation * flow in a `try/catch/finally` construction. */ - ControlFlowGraph::ControlFlowNode getAControlFlowNode() { + ControlFlow::Node getAControlFlowNode() { result.getElement() = this } /** * Gets a first control flow node executed within this element. */ - ControlFlowGraph::ControlFlowNode getAControlFlowEntryNode() { + ControlFlow::Node getAControlFlowEntryNode() { result = ControlFlowGraph::Internal::getAControlFlowEntryNode(this).getAControlFlowNode() } /** * Gets a potential last control flow node executed within this element. */ - ControlFlowGraph::ControlFlowNode getAControlFlowExitNode() { + ControlFlow::Node getAControlFlowExitNode() { result = ControlFlowGraph::Internal::getAControlFlowExitNode(this).getAControlFlowNode() } @@ -61,7 +59,7 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element { /** Gets an element that is reachable from this element. */ ControlFlowElement getAReachableElement() { // Reachable in same basic block - exists(ControlFlowGraph::BasicBlock bb, int i, int j | + exists(ControlFlow::BasicBlock bb, int i, int j | bb.getNode(i) = getAControlFlowNode() and bb.getNode(j) = result.getAControlFlowNode() and i < j diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index 1a44b35ce412..d06c7388266c 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -1,3165 +1,3253 @@ -/** - * Provides classes representing the control flow graph within callables. - */ - import csharp -import BasicBlocks -private import Completion /** - * A control flow node. - * - * Either a callable entry node (`CallableEntryNode`), a callable exit node - * (`CallableExitNode`), a control flow node for a control flow element, that is, - * an expression or a statement (`ControlFlowElementNode`). - * - * A control flow node is a node in the control flow graph (CFG). There is a - * many-to-one relationship between `ControlFlowElementNode`s and `ControlFlowElement`s. - * This allows control flow splitting, for example modeling the control flow through - * `finally` blocks. + * Provides classes representing the control flow graph within callables. */ -class ControlFlowNode extends TControlFlowNode { - /** Gets a textual representation of this control flow node. */ - string toString() { none() } - - /** Gets the control flow element that this node corresponds to, if any. */ - ControlFlowElement getElement() { none() } - - /** Gets the location of this control flow node. */ - Location getLocation() { result = getElement().getLocation() } - - /** Holds if this control flow node has conditional successors. */ - predicate isCondition() { - exists(getASuccessorByType(any(ControlFlowEdgeConditional e))) - } - - /** Gets the basic block that this control flow node belongs to. */ - BasicBlock getBasicBlock() { - result.getANode() = this - } +module ControlFlow { + private import semmle.code.csharp.controlflow.BasicBlocks as BBs + private import semmle.code.csharp.controlflow.Completion /** - * Holds if this node dominates `that` node. - * - * That is, all paths reaching `that` node from some callable entry - * node (`CallableEntryNode`) must go through this node. + * A control flow node. * - * Example: + * Either a callable entry node (`EntryNode`), a callable exit node (`ExitNode`), + * or a control flow node for a control flow element, that is, an expression or a + * statement (`ElementNode`). * - * ``` - * int M(string s) - * { - * if (s == null) - * throw new ArgumentNullException(nameof(s)); - * return s.Length; - * } - * ``` + * A control flow node is a node in the control flow graph (CFG). There is a + * many-to-one relationship between `ElementNode`s and `ControlFlowElement`s. + * This allows control flow splitting, for example modeling the control flow + * through `finally` blocks. * - * The node on line 3 dominates the node on line 5 (all paths from the - * entry point of `M` to `return s.Length;` must go through the null check). - * - * This predicate is *reflexive*, so for example `if (s == null)` dominates - * itself. + * Only nodes that can be reached from the callable entry point are included in + * the CFG. */ - pragma [inline] // potentially very large predicate, so must be inlined - predicate dominates(ControlFlowNode that) { - strictlyDominates(that) - or - this = that - } + class Node extends TNode { + /** Gets a textual representation of this control flow node. */ + string toString() { none() } - /** - * Holds if this node strictly dominates `that` node. - * - * That is, all paths reaching `that` node from some callable entry - * node (`CallableEntryNode`) must go through this node (which must - * be different from `that` node). - * - * Example: - * - * ``` - * int M(string s) - * { - * if (s == null) - * throw new ArgumentNullException(nameof(s)); - * return s.Length; - * } - * ``` - * - * The node on line 3 strictly dominates the node on line 5 - * (all paths from the entry point of `M` to `return s.Length;` must go - * through the null check). - */ - pragma [inline] // potentially very large predicate, so must be inlined - predicate strictlyDominates(ControlFlowNode that) { - this.getBasicBlock().strictlyDominates(that.getBasicBlock()) - or - exists(BasicBlock bb, int i, int j | - bb.getNode(i) = this - and - bb.getNode(j) = that - and - i < j - ) - } + /** Gets the control flow element that this node corresponds to, if any. */ + ControlFlowElement getElement() { none() } - /** - * Holds if this node post-dominates `that` node. - * - * That is, all paths reaching a callable exit node (`CallableExitNode`) - * from `that` node must go through this node. - * - * Example: - * - * ``` - * int M(string s) - * { - * try - * { - * return s.Length; - * } - * finally - * { - * Console.WriteLine("M"); - * } - * } - * ``` - * - * The node on line 9 post-dominates the node on line 5 (all paths to the - * exit point of `M` from `return s.Length;` must go through the `WriteLine` - * call). - * - * This predicate is *reflexive*, so for example `Console.WriteLine("M");` - * post-dominates itself. - */ - pragma [inline] // potentially very large predicate, so must be inlined - predicate postDominates(ControlFlowNode that) { - strictlyPostDominates(that) - or - this = that - } + /** Gets the location of this control flow node. */ + Location getLocation() { result = getElement().getLocation() } - /** - * Holds if this node strictly post-dominates `that` node. - * - * That is, all paths reaching a callable exit node (`CallableExitNode`) - * from `that` node must go through this node (which must be different - * from `that` node). - * - * Example: - * - * ``` - * int M(string s) - * { - * try - * { - * return s.Length; - * } - * finally - * { - * Console.WriteLine("M"); - * } - * } - * ``` - * - * The node on line 9 strictly post-dominates the node on line 5 (all - * paths to the exit point of `M` from `return s.Length;` must go through - * the `WriteLine` call). - */ - pragma [inline] // potentially very large predicate, so must be inlined - predicate strictlyPostDominates(ControlFlowNode that) { - this.getBasicBlock().strictlyPostDominates(that.getBasicBlock()) - or - exists(BasicBlock bb, int i, int j | - bb.getNode(i) = this - and - bb.getNode(j) = that - and - i > j - ) - } + /** Holds if this control flow node has conditional successors. */ + predicate isCondition() { + exists(getASuccessorByType(any(SuccessorTypes::ConditionalSuccessor e))) + } - /** Gets a successor node of a given flow type, if any. */ - ControlFlowNode getASuccessorByType(ControlFlowEdgeType t) { - result = getASuccessorByType(this, t) - } + /** Gets the basic block that this control flow node belongs to. */ + BasicBlock getBasicBlock() { + result.getANode() = this + } - /** Gets an immediate successor, if any. */ - ControlFlowNode getASuccessor() { result = getASuccessorByType(_) } + /** + * Holds if this node dominates `that` node. + * + * That is, all paths reaching `that` node from some callable entry + * node (`EntryNode`) must go through this node. + * + * Example: + * + * ``` + * int M(string s) + * { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The node on line 3 dominates the node on line 5 (all paths from the + * entry point of `M` to `return s.Length;` must go through the null check). + * + * This predicate is *reflexive*, so for example `if (s == null)` dominates + * itself. + */ + pragma [inline] // potentially very large predicate, so must be inlined + predicate dominates(Node that) { + strictlyDominates(that) + or + this = that + } - /** Gets an immediate predecessor node of a given flow type, if any. */ - ControlFlowNode getAPredecessorByType(ControlFlowEdgeType t) { - result.getASuccessorByType(t) = this - } + /** + * Holds if this node strictly dominates `that` node. + * + * That is, all paths reaching `that` node from some callable entry + * node (`EntryNode`) must go through this node (which must + * be different from `that` node). + * + * Example: + * + * ``` + * int M(string s) + * { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The node on line 3 strictly dominates the node on line 5 + * (all paths from the entry point of `M` to `return s.Length;` must go + * through the null check). + */ + pragma [inline] // potentially very large predicate, so must be inlined + predicate strictlyDominates(Node that) { + this.getBasicBlock().strictlyDominates(that.getBasicBlock()) + or + exists(BasicBlock bb, int i, int j | + bb.getNode(i) = this + and + bb.getNode(j) = that + and + i < j + ) + } - /** Gets an immediate predecessor, if any. */ - ControlFlowNode getAPredecessor() { result = getAPredecessorByType(_) } + /** + * Holds if this node post-dominates `that` node. + * + * That is, all paths reaching a callable exit node (`ExitNode`) + * from `that` node must go through this node. + * + * Example: + * + * ``` + * int M(string s) + * { + * try + * { + * return s.Length; + * } + * finally + * { + * Console.WriteLine("M"); + * } + * } + * ``` + * + * The node on line 9 post-dominates the node on line 5 (all paths to the + * exit point of `M` from `return s.Length;` must go through the `WriteLine` + * call). + * + * This predicate is *reflexive*, so for example `Console.WriteLine("M");` + * post-dominates itself. + */ + pragma [inline] // potentially very large predicate, so must be inlined + predicate postDominates(Node that) { + strictlyPostDominates(that) + or + this = that + } - /** - * Gets an immediate `true` successor, if any. - * - * An immediate `true` successor is a successor that is reached when - * this condition evaluates to `true`. - * - * Example: - * - * ``` - * if (x < 0) - * x = -x; - * ``` - * - * The node on line 2 is an immediate `true` successor of the node - * on line 1. - */ - ControlFlowNode getATrueSuccessor() { - result = getASuccessorByType(any(ControlFlowEdgeBoolean e | e.getValue() = true)) - } + /** + * Holds if this node strictly post-dominates `that` node. + * + * That is, all paths reaching a callable exit node (`ExitNode`) + * from `that` node must go through this node (which must be different + * from `that` node). + * + * Example: + * + * ``` + * int M(string s) + * { + * try + * { + * return s.Length; + * } + * finally + * { + * Console.WriteLine("M"); + * } + * } + * ``` + * + * The node on line 9 strictly post-dominates the node on line 5 (all + * paths to the exit point of `M` from `return s.Length;` must go through + * the `WriteLine` call). + */ + pragma [inline] // potentially very large predicate, so must be inlined + predicate strictlyPostDominates(Node that) { + this.getBasicBlock().strictlyPostDominates(that.getBasicBlock()) + or + exists(BasicBlock bb, int i, int j | + bb.getNode(i) = this + and + bb.getNode(j) = that + and + i > j + ) + } - /** - * Gets an immediate `false` successor, if any. - * - * An immediate `false` successor is a successor that is reached when - * this condition evaluates to `false`. - * - * Example: - * - * ``` - * if (!(x >= 0)) - * x = -x; - * ``` - * - * The node on line 2 is an immediate `false` successor of the node - * on line 1. - */ - ControlFlowNode getAFalseSuccessor() { - result = getASuccessorByType(any(ControlFlowEdgeBoolean e | e.getValue() = false)) - } + /** Gets a successor node of a given flow type, if any. */ + Node getASuccessorByType(SuccessorType t) { + result = getASuccessorByType(this, t) + } - /** - * Gets an immediate `null` successor, if any. - * - * An immediate `null` successor is a successor that is reached when - * this expression evaluates to `null`. - * - * Example: - * - * ``` - * x?.M(); - * return; - * ``` - * - * The node on line 2 is an immediate `null` successor of the node - * `x` on line 1. - */ - ControlFlowNode getANullSuccessor() { - result = getASuccessorByType(any(ControlFlowEdgeNullness e | e.isNull())) - } + /** Gets an immediate successor, if any. */ + Node getASuccessor() { result = getASuccessorByType(_) } - /** - * Gets an immediate non-`null` successor, if any. - * - * An immediate non-`null` successor is a successor that is reached when - * this expressions evaluates to a non-`null` value. - * - * Example: - * - * ``` - * x?.M(); - * ``` - * - * The node `x?.M()`, representing the call to `M`, is a non-`null` successor - * of the node `x`. - */ - ControlFlowNode getANonNullSuccessor() { - result = getASuccessorByType(any(ControlFlowEdgeNullness e | not e.isNull())) - } + /** Gets an immediate predecessor node of a given flow type, if any. */ + Node getAPredecessorByType(SuccessorType t) { + result.getASuccessorByType(t) = this + } - /** Holds if this node has more than one predecessor. */ - predicate isJoin() { - strictcount(getAPredecessor()) > 1 - } -} + /** Gets an immediate predecessor, if any. */ + Node getAPredecessor() { result = getAPredecessorByType(_) } -/** A node for a callable entry point. */ -class CallableEntryNode extends ControlFlowNode, TCallableEntry { - /** Gets the callable that this entry applies to. */ - Callable getCallable() { this = TCallableEntry(result) } + /** + * Gets an immediate `true` successor, if any. + * + * An immediate `true` successor is a successor that is reached when + * this condition evaluates to `true`. + * + * Example: + * + * ``` + * if (x < 0) + * x = -x; + * ``` + * + * The node on line 2 is an immediate `true` successor of the node + * on line 1. + */ + Node getATrueSuccessor() { + result = getASuccessorByType(any(SuccessorTypes::BooleanSuccessor t | t.getValue() = true)) + } - override EntryBasicBlock getBasicBlock() { - result = ControlFlowNode.super.getBasicBlock() - } + /** + * Gets an immediate `false` successor, if any. + * + * An immediate `false` successor is a successor that is reached when + * this condition evaluates to `false`. + * + * Example: + * + * ``` + * if (!(x >= 0)) + * x = -x; + * ``` + * + * The node on line 2 is an immediate `false` successor of the node + * on line 1. + */ + Node getAFalseSuccessor() { + result = getASuccessorByType(any(SuccessorTypes::BooleanSuccessor t | t.getValue() = false)) + } - override Location getLocation() { - result = getCallable().getLocation() - } + /** + * Gets an immediate `null` successor, if any. + * + * An immediate `null` successor is a successor that is reached when + * this expression evaluates to `null`. + * + * Example: + * + * ``` + * x?.M(); + * return; + * ``` + * + * The node on line 2 is an immediate `null` successor of the node + * `x` on line 1. + */ + Node getANullSuccessor() { + result = getASuccessorByType(any(SuccessorTypes::NullnessSuccessor t | t.isNull())) + } + + /** + * Gets an immediate non-`null` successor, if any. + * + * An immediate non-`null` successor is a successor that is reached when + * this expressions evaluates to a non-`null` value. + * + * Example: + * + * ``` + * x?.M(); + * ``` + * + * The node `x?.M()`, representing the call to `M`, is a non-`null` successor + * of the node `x`. + */ + Node getANonNullSuccessor() { + result = getASuccessorByType(any(SuccessorTypes::NullnessSuccessor t | not t.isNull())) + } - override string toString() { - result = "enter " + getCallable().toString() + /** Holds if this node has more than one predecessor. */ + predicate isJoin() { + strictcount(getAPredecessor()) > 1 + } } -} -/** A node for a callable exit point. */ -class CallableExitNode extends ControlFlowNode, TCallableExit { - /** Gets the callable that this exit applies to. */ - Callable getCallable() { this = TCallableExit(result) } + /** Provides different types of control flow nodes. */ + module Nodes { + /** A node for a callable entry point. */ + class EntryNode extends Node, TEntryNode { + /** Gets the callable that this entry applies to. */ + Callable getCallable() { this = TEntryNode(result) } - override ExitBasicBlock getBasicBlock() { - result = ControlFlowNode.super.getBasicBlock() - } + override BasicBlocks::EntryBlock getBasicBlock() { + result = Node.super.getBasicBlock() + } - override Location getLocation() { - result = getCallable().getLocation() - } + override Location getLocation() { + result = getCallable().getLocation() + } - override string toString() { - result = "exit " + getCallable().toString() - } -} + override string toString() { + result = "enter " + getCallable().toString() + } + } -/** - * A node for a control flow element, that is, an expression or a statement. - * - * Each control flow element maps to zero or more `ControlFlowElementNode`s: - * zero when the element is in unreachable (dead) code, and multiple when there - * are different splits for the element. - */ -class ControlFlowElementNode extends ControlFlowNode, TNode { - private Splits splits; - private ControlFlowElement cfe; + /** A node for a callable exit point. */ + class ExitNode extends Node, TExitNode { + /** Gets the callable that this exit applies to. */ + Callable getCallable() { this = TExitNode(result) } - ControlFlowElementNode() { - this = TNode(cfe, splits) - } + override BasicBlocks::ExitBlock getBasicBlock() { + result = Node.super.getBasicBlock() + } - override ControlFlowElement getElement() { - result = cfe - } + override Location getLocation() { + result = getCallable().getLocation() + } - override string toString() { - exists(string s | - s = splits.toString() | - if s = "" then - result = cfe.toString() - else - result = "[" + s + "] " + cfe.toString() - ) - } + override string toString() { + result = "exit " + getCallable().toString() + } + } - /** Gets a split for this control flow node, if any. */ - Split getASplit() { - result = splits.getASplit() - } -} + /** + * A node for a control flow element, that is, an expression or a statement. + * + * Each control flow element maps to zero or more `ElementNode`s: zero when + * the element is in unreachable (dead) code, and multiple when there are + * different splits for the element. + */ + class ElementNode extends Node, TElementNode { + private Splits splits; + private ControlFlowElement cfe; -class Split = SplitImpl; -class FinallySplit = FinallySplitting::FinallySplitImpl; -class ExceptionHandlerSplit = ExceptionHandlerSplitting::ExceptionHandlerSplitImpl; + ElementNode() { + this = TElementNode(cfe, splits) + } -/** - * DEPRECATED: Use `ControlFlowElementNode` instead. - * - * A node for a control flow element. - */ -deprecated -class NormalControlFlowNode extends ControlFlowElementNode { - NormalControlFlowNode() { - forall(FinallySplit s | - s = this.getASplit() | - s.getType() instanceof ControlFlowEdgeSuccessor - ) + override ControlFlowElement getElement() { + result = cfe + } + + override string toString() { + exists(string s | + s = splits.toString() | + if s = "" then + result = cfe.toString() + else + result = "[" + s + "] " + cfe.toString() + ) + } + + /** Gets a split for this control flow node, if any. */ + Split getASplit() { + result = splits.getASplit() + } + } + + class Split = SplitImpl; + class FinallySplit = FinallySplitting::FinallySplitImpl; + class ExceptionHandlerSplit = ExceptionHandlerSplitting::ExceptionHandlerSplitImpl; } -} -/** - * DEPRECATED: Use `ControlFlowElementNode` instead. - * - * A split node for a control flow element that belongs to a `finally` block. - */ -deprecated -class FinallySplitControlFlowNode extends ControlFlowElementNode { - FinallySplitControlFlowNode() { - exists(FinallySplitting::FinallySplitType type | - type = this.getASplit().(FinallySplit).getType() | - not type instanceof ControlFlowEdgeSuccessor - ) + class BasicBlock = BBs::BasicBlock; + + /** Provides different types of basic blocks. */ + module BasicBlocks { + class EntryBlock = BBs::EntryBasicBlock; + class ExitBlock = BBs::ExitBasicBlock; + class JoinBlock = BBs::JoinBlock; + class ConditionBlock = BBs::ConditionBlock; } - /** Gets the try statement that this `finally` node belongs to. */ - TryStmt getTryStmt() { - this.getElement() = FinallySplitting::getAFinallyDescendant(result) + /** The type of a control flow successor. */ + class SuccessorType extends TSuccessorType { + /** Gets a textual representation of successor type. */ + string toString() { none() } + + /** Holds if this successor type matches completion `c`. */ + predicate matchesCompletion(Completion c) { none() } } -} -/** The type of an edge in the control flow graph. */ -class ControlFlowEdgeType extends TControlFlowEdgeType { - /** Gets a textual representation of this control flow edge type. */ - string toString() { none() } + /** Provides different types of control flow successor types. */ + module SuccessorTypes { + /** A normal control flow successor. */ + class NormalSuccessor extends SuccessorType, TSuccessorSuccessor { + override string toString() { result = "successor" } - /** Holds if this edge type matches completion `c`. */ - predicate matchesCompletion(Completion c) { none() } -} + override predicate matchesCompletion(Completion c) { + c instanceof NormalCompletion and + not c instanceof ConditionalCompletion and + not c instanceof BreakNormalCompletion + } + } -/** A normal control flow edge. */ -class ControlFlowEdgeSuccessor extends ControlFlowEdgeType, TSuccessorEdge { - override string toString() { result = "successor" } + /** + * A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`), + * a nullness successor (`NullnessSuccessor`), a matching successor (`MatchingSuccessor`), + * or an emptiness successor (`EmptinessSuccessor`). + */ + abstract class ConditionalSuccessor extends SuccessorType { } - override predicate matchesCompletion(Completion c) { - c instanceof NormalCompletion and - not c instanceof ConditionalCompletion and - not c instanceof BreakNormalCompletion - } -} + /** + * A Boolean control flow successor. + * + * For example, this program fragment: + * + * ``` + * if (x < 0) + * return 0; + * else + * return 1; + * ``` + * + * has a control flow graph containing Boolean successors: + * + * ``` + * if + * | + * x < 0 + * / \ + * / \ + * / \ + * true false + * | \ + * return 0 return 1 + * ``` + */ + class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { + /** Gets the value of this Boolean successor. */ + boolean getValue() { this = TBooleanSuccessor(result) } -/** - * A conditional control flow edge. Either a Boolean edge (`ControlFlowEdgeBoolean`), - * a nullness edge (`ControlFlowEdgeNullness`), a matching edge (`ControlFlowEdgeMatching`), - * or an emptiness edge (`ControlFlowEdgeEmptiness`). - */ -abstract class ControlFlowEdgeConditional extends ControlFlowEdgeType { } + override string toString() { result = getValue().toString() } -/** - * A Boolean control flow edge. - * - * For example, this program fragment: - * - * ``` - * if (x < 0) - * return 0; - * else - * return 1; - * ``` - * - * has a control flow graph containing Boolean edges: - * - * ``` - * if - * | - * x < 0 - * / \ - * / \ - * / \ - * true false - * | \ - * return 0 return 1 - * ``` - */ -class ControlFlowEdgeBoolean extends ControlFlowEdgeConditional, TBooleanEdge { - /** Gets the value of this Boolean edge. */ - boolean getValue() { this = TBooleanEdge(result) } + override predicate matchesCompletion(Completion c) { + c.(BooleanCompletion).getInnerValue() = this.getValue() + } + } - override string toString() { result = getValue().toString() } + /** + * A nullness control flow successor. + * + * For example, this program fragment: + * + * ``` + * int? M(string s) => s?.Length; + * ``` + * + * has a control flow graph containing nullness successors: + * + * ``` + * enter M + * | + * s + * / \ + * / \ + * / \ + * null non-null + * \ | + * \ Length + * \ / + * \ / + * exit M + * ``` + */ + class NullnessSuccessor extends ConditionalSuccessor, TNullnessSuccessor { + /** Holds if this is a `null` successor. */ + predicate isNull() { this = TNullnessSuccessor(true) } - override predicate matchesCompletion(Completion c) { - c.(BooleanCompletion).getInnerValue() = this.getValue() - } -} + override string toString() { + if this.isNull() then + result = "null" + else + result = "non-null" + } -/** - * A nullness control flow edge. - * - * For example, this program fragment: - * - * ``` - * int? M(string s) => s?.Length; - * ``` - * - * has a control flow graph containing nullness edges: - * - * ``` - * enter M - * | - * s - * / \ - * / \ - * / \ - * null non-null - * \ | - * \ Length - * \ / - * \ / - * exit M - * ``` - */ -class ControlFlowEdgeNullness extends ControlFlowEdgeConditional, TNullnessEdge { - /** Holds if this is a `null` edge. */ - predicate isNull() { this = TNullnessEdge(true) } - - override string toString() { - if this.isNull() then - result = "null" - else - result = "non-null" - } + override predicate matchesCompletion(Completion c) { + if this.isNull() then + c.(NullnessCompletion).isNull() + else + c = any(NullnessCompletion nc | not nc.isNull()) + } + } - override predicate matchesCompletion(Completion c) { - if this.isNull() then - c.(NullnessCompletion).isNull() - else - c = any(NullnessCompletion nc | not nc.isNull()) - } -} + /** + * A matching control flow successor. + * + * For example, this program fragment: + * + * ``` + * switch (x) { + * case 0 : + * return 0; + * default : + * return 1; + * } + * ``` + * + * has a control flow graph containing macthing successors: + * + * ``` + * switch + * | + * x + * | + * case 0 + * / \ + * / \ + * / \ + * match no-match + * | \ + * return 0 default + * | + * return 1 + * ``` + */ + class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor { + /** Holds if this is a match successor. */ + predicate isMatch() { this = TMatchingSuccessor(true) } -/** - * A matching control flow edge. - * - * For example, this program fragment: - * - * ``` - * switch (x) { - * case 0 : - * return 0; - * default : - * return 1; - * } - * ``` - * - * has a control flow graph containing macthing edges: - * - * ``` - * switch - * | - * x - * | - * case 0 - * / \ - * / \ - * / \ - * match no-match - * | \ - * return 0 default - * | - * return 1 - * ``` - */ -class ControlFlowEdgeMatching extends ControlFlowEdgeConditional, TMatchingEdge { - /** Holds if this is a match edge. */ - predicate isMatch() { this = TMatchingEdge(true) } - - override string toString() { - if this.isMatch() then - result = "match" - else - result = "no-match" - } + override string toString() { + if this.isMatch() then + result = "match" + else + result = "no-match" + } - override predicate matchesCompletion(Completion c) { - if this.isMatch() then - c.(MatchingCompletion).isMatch() - else - c = any(MatchingCompletion mc | not mc.isMatch()) - } -} + override predicate matchesCompletion(Completion c) { + if this.isMatch() then + c.(MatchingCompletion).isMatch() + else + c = any(MatchingCompletion mc | not mc.isMatch()) + } + } -/** - * An emptiness control flow edge. - * - * For example, this program fragment: - * - * ``` - * foreach (var arg in args) - * { - * yield return arg; - * } - * yield return ""; - * ``` - * - * has a control flow graph containing emptiness edges: - * - * ``` - * args - * | - * foreach------<------- - * / \ \ - * / \ | - * / \ | - * / \ | - * empty non-empty | - * | \ | - * yield return "" \ | - * var arg | - * | | - * yield return arg | - * \_________/ - * ``` - */ -class ControlFlowEdgeEmptiness extends ControlFlowEdgeConditional, TEmptinessEdge { - /** Holds if this is an empty edge. */ - predicate isEmpty() { this = TEmptinessEdge(true) } - - override string toString() { - if this.isEmpty() then - result = "empty" - else - result = "non-empty" - } + /** + * An emptiness control flow successor. + * + * For example, this program fragment: + * + * ``` + * foreach (var arg in args) + * { + * yield return arg; + * } + * yield return ""; + * ``` + * + * has a control flow graph containing emptiness successors: + * + * ``` + * args + * | + * foreach------<------- + * / \ \ + * / \ | + * / \ | + * / \ | + * empty non-empty | + * | \ | + * yield return "" \ | + * var arg | + * | | + * yield return arg | + * \_________/ + * ``` + */ + class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor { + /** Holds if this is an empty successor. */ + predicate isEmpty() { this = TEmptinessSuccessor(true) } - override predicate matchesCompletion(Completion c) { - if this.isEmpty() then - c.(EmptinessCompletion).isEmpty() - else - c = any(EmptinessCompletion ec | not ec.isEmpty()) - } -} + override string toString() { + if this.isEmpty() then + result = "empty" + else + result = "non-empty" + } -/** - * A `return` control flow edge. - * - * Example: - * - * ``` - * void M() - * { - * return; - * } - * ``` - * - * There is a `return` edge from `return;` to the callable exit node - * of `M`. - */ -class ControlFlowEdgeReturn extends ControlFlowEdgeType, TReturnEdge { - override string toString() { result = "return" } + override predicate matchesCompletion(Completion c) { + if this.isEmpty() then + c.(EmptinessCompletion).isEmpty() + else + c = any(EmptinessCompletion ec | not ec.isEmpty()) + } + } - override predicate matchesCompletion(Completion c) { - c instanceof ReturnCompletion - } -} + /** + * A `return` control flow successor. + * + * Example: + * + * ``` + * void M() + * { + * return; + * } + * ``` + * + * The callable exit node of `M` is a `return` successor of the `return;` + * statement. + */ + class ReturnSuccessor extends SuccessorType, TReturnSuccessor { + override string toString() { result = "return" } -/** - * A `break` control flow edge. - * - * Example: - * - * ``` - * int M(int x) - * { - * while (true) - * { - * if (x++ > 10) - * break; - * } - * return x; - * } - * ``` - * - * There is a `break` edge from `break;` to `return x;`. - */ -class ControlFlowEdgeBreak extends ControlFlowEdgeType, TBreakEdge { - override string toString() { result = "break" } + override predicate matchesCompletion(Completion c) { + c instanceof ReturnCompletion + } + } - override predicate matchesCompletion(Completion c) { - c instanceof BreakCompletion or - c instanceof BreakNormalCompletion - } -} + /** + * A `break` control flow successor. + * + * Example: + * + * ``` + * int M(int x) + * { + * while (true) + * { + * if (x++ > 10) + * break; + * } + * return x; + * } + * ``` + * + * The node `return x;` is a `break` succedssor of the node `break;`. + */ + class BreakSuccessor extends SuccessorType, TBreakSuccessor { + override string toString() { result = "break" } -/** - * A `continue` control flow edge. - * - * Example: - * - * ``` - * int M(int x) - * { - * while (true) { - * if (x++ < 10) - * continue; - * } - * return x; - * } - * ``` - * - * There is a `continue` edge from `continue;` to `while (true) { ... }`. - */ -class ControlFlowEdgeContinue extends ControlFlowEdgeType, TContinueEdge { - override string toString() { result = "continue" } + override predicate matchesCompletion(Completion c) { + c instanceof BreakCompletion or + c instanceof BreakNormalCompletion + } + } - override predicate matchesCompletion(Completion c) { - c instanceof ContinueCompletion - } -} + /** + * A `continue` control flow successor. + * + * Example: + * + * ``` + * int M(int x) + * { + * while (true) { + * if (x++ < 10) + * continue; + * } + * return x; + * } + * ``` + * + * The node `while (true) { ... }` is a `continue` successor of the node + * `continue;`. + */ + class ContinueSuccessor extends SuccessorType, TContinueSuccessor { + override string toString() { result = "continue" } -/** - * A `goto label` control flow edge. - * - * Example: - * - * ``` - * int M(int x) - * { - * while (true) - * { - * if (x++ > 10) - * goto Return; - * } - * Return: return x; - * } - * ``` - * - * There is a `goto label` edge from `goto Return;` to - * `Return: return x`. - */ -class ControlFlowEdgeGotoLabel extends ControlFlowEdgeType, TGotoLabelEdge { - /** Gets the statement that resulted in this `goto` edge. */ - GotoLabelStmt getGotoStmt() { this = TGotoLabelEdge(result) } + override predicate matchesCompletion(Completion c) { + c instanceof ContinueCompletion + } + } - override string toString() { result = "goto(" + getGotoStmt().getLabel() + ")" } + /** + * A `goto label` control flow successor. + * + * Example: + * + * ``` + * int M(int x) + * { + * while (true) + * { + * if (x++ > 10) + * goto Return; + * } + * Return: return x; + * } + * ``` + * + * The node `Return: return x` is a `goto label` successor of the node + * `goto Return;`. + */ + class GotoLabelSuccessor extends SuccessorType, TGotoLabelSuccessor { + /** Gets the statement that resulted in this `goto` successor. */ + GotoLabelStmt getGotoStmt() { this = TGotoLabelSuccessor(result) } - override predicate matchesCompletion(Completion c) { - c.(GotoLabelCompletion).getGotoStmt() = getGotoStmt() - } -} + override string toString() { result = "goto(" + getGotoStmt().getLabel() + ")" } -/** - * A `goto case` control flow edge. - * - * Example: - * - * ``` - * switch (x) - * { - * case 0 : return 1; - * case 1 : goto case 0; - * default : return -1; - * } - * ``` - * - * There is a `goto case` edge from `goto case 0;` to - * `case 0 : return 1;`. - */ -class ControlFlowEdgeGotoCase extends ControlFlowEdgeType, TGotoCaseEdge { - /** Gets the statement that resulted in this `goto case` edge. */ - GotoCaseStmt getGotoStmt() { this = TGotoCaseEdge(result) } + override predicate matchesCompletion(Completion c) { + c.(GotoLabelCompletion).getGotoStmt() = getGotoStmt() + } + } - override string toString() { result = "goto(" + getGotoStmt().getLabel() + ")" } + /** + * A `goto case` control flow successor. + * + * Example: + * + * ``` + * switch (x) + * { + * case 0 : return 1; + * case 1 : goto case 0; + * default : return -1; + * } + * ``` + * + * The node `case 0 : return 1;` is a `goto case` successor of the node + * `goto case 0;`. + */ + class GotoCaseSuccessor extends SuccessorType, TGotoCaseSuccessor { + /** Gets the statement that resulted in this `goto case` successor. */ + GotoCaseStmt getGotoStmt() { this = TGotoCaseSuccessor(result) } - override predicate matchesCompletion(Completion c) { - c.(GotoCaseCompletion).getGotoStmt() = getGotoStmt() - } -} + override string toString() { result = "goto(" + getGotoStmt().getLabel() + ")" } -/** - * A `goto default` control flow edge. - * - * Example: - * - * ``` - * switch (x) - * { - * case 0 : return 1; - * case 1 : goto default; - * default : return -1; - * } - * ``` - * - * There is a `goto default` edge from `goto default;` to - * `default : return -1;`. - */ -class ControlFlowEdgeGotoDefault extends ControlFlowEdgeType, TGotoDefaultEdge { - override string toString() { result = "goto default" } + override predicate matchesCompletion(Completion c) { + c.(GotoCaseCompletion).getGotoStmt() = getGotoStmt() + } + } - override predicate matchesCompletion(Completion c) { - c instanceof GotoDefaultCompletion - } -} + /** + * A `goto default` control flow successor. + * + * Example: + * + * ``` + * switch (x) + * { + * case 0 : return 1; + * case 1 : goto default; + * default : return -1; + * } + * ``` + * + * The node `default : return -1;` is a `goto default` successor of the node + * `goto default;`. + */ + class GotoDefaultSuccessor extends SuccessorType, TGotoDefaultSuccessor { + override string toString() { result = "goto default" } -/** - * An exceptional control flow edge. - * - * Example: - * - * ``` - * int M(string s) - * { - * if (s == null) - * throw new ArgumentNullException(nameof(s)); - * return s.Length; - * } - * ``` - * - * There is an exceptional edge (of type `ArgumentNullException`) from - * `throw new ArgumentNullException(nameof(s));` to the callable exit node - * of `M`. - */ -class ControlFlowEdgeException extends ControlFlowEdgeType, TExceptionEdge { - /** Gets the type of exception. */ - ExceptionClass getExceptionClass() { this = TExceptionEdge(result) } + override predicate matchesCompletion(Completion c) { + c instanceof GotoDefaultCompletion + } + } - override string toString() { result = "exception(" + getExceptionClass().getName() + ")" } + /** + * An exceptional control flow successor. + * + * Example: + * + * ``` + * int M(string s) + * { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The callable exit node of `M` is an exceptional successor (of type + * `ArgumentNullException`) of the node `throw new ArgumentNullException(nameof(s));`. + */ + class ExceptionSuccessor extends SuccessorType, TExceptionSuccessor { + /** Gets the type of exception. */ + ExceptionClass getExceptionClass() { this = TExceptionSuccessor(result) } - override predicate matchesCompletion(Completion c) { - c.(ThrowCompletion).getExceptionClass() = getExceptionClass() + override string toString() { result = "exception(" + getExceptionClass().getName() + ")" } + + override predicate matchesCompletion(Completion c) { + c.(ThrowCompletion).getExceptionClass() = getExceptionClass() + } + } } -} -/** - * INTERNAL: Do not use. - */ -module Internal { /** - * Provides auxiliary classes and predicates used to construct the basic successor - * relation on control flow elements. - * - * The implementation is centered around the concept of a _completion_, which - * models how the execution of a statement or expression terminates. - * Completions are represented as an algebraic data type `Completion` defined in - * `Completion.qll`. - * - * The CFG is built by structural recursion over the AST. To achieve this the - * CFG edges related to a given AST node, `n`, are divided into three categories: - * - * 1. The in-going edge that points to the first CFG node to execute when - * `n` is going to be executed. - * 2. The out-going edges for control flow leaving `n` that are going to some - * other node in the surrounding context of `n`. - * 3. The edges that have both of their end-points entirely within the AST - * node and its children. - * - * The edges in (1) and (2) are inherently non-local and are therefore - * initially calculated as half-edges, that is, the single node, `k`, of the - * edge contained within `n`, by the predicates `k = first(n)` and `k = last(n, _)`, - * respectively. The edges in (3) can then be enumerated directly by the predicate - * `succ` by calling `first` and `last` recursively on the children of `n` and - * connecting the end-points. This yields the entire CFG, since all edges are in - * (3) for _some_ AST node. - * - * The second parameter of `last` is the completion, which is necessary to distinguish - * the out-going edges from `n`. Note that the completion changes as the calculation of - * `last` proceeds outward through the AST; for example, a `BreakCompletion` is - * caught up by its surrounding loop and turned into a `NormalCompletion`, and a - * `NormalCompletion` proceeds outward through the end of a `finally` block and is - * turned into whatever completion was caught by the `finally`. - * - * An important goal of the CFG is to get the order of side-effects correct. - * Most expressions can have side-effects and must therefore be modeled in the - * CFG in AST post-order. For example, a `MethodCall` evaluates its arguments - * before the call. Most statements do not have side-effects, but merely affect - * the control flow and some could therefore be excluded from the CFG. However, - * as a design choice, all statements are included in the CFG and generally - * serve as their own entry-points, thus executing in some version of AST - * pre-order. + * INTERNAL: Do not use. */ - private module Successor { + module Internal { /** - * A control flow element where the children are evaluated following a - * standard left-to-right evaluation. The actual evaluation order is - * determined by the predicate `getChildElement()`. + * Provides auxiliary classes and predicates used to construct the basic successor + * relation on control flow elements. + * + * The implementation is centered around the concept of a _completion_, which + * models how the execution of a statement or expression terminates. + * Completions are represented as an algebraic data type `Completion` defined in + * `Completion.qll`. + * + * The CFG is built by structural recursion over the AST. To achieve this the + * CFG edges related to a given AST node, `n`, are divided into three categories: + * + * 1. The in-going edge that points to the first CFG node to execute when + * `n` is going to be executed. + * 2. The out-going edges for control flow leaving `n` that are going to some + * other node in the surrounding context of `n`. + * 3. The edges that have both of their end-points entirely within the AST + * node and its children. + * + * The edges in (1) and (2) are inherently non-local and are therefore + * initially calculated as half-edges, that is, the single node, `k`, of the + * edge contained within `n`, by the predicates `k = first(n)` and `k = last(n, _)`, + * respectively. The edges in (3) can then be enumerated directly by the predicate + * `succ` by calling `first` and `last` recursively on the children of `n` and + * connecting the end-points. This yields the entire CFG, since all edges are in + * (3) for _some_ AST node. + * + * The second parameter of `last` is the completion, which is necessary to distinguish + * the out-going edges from `n`. Note that the completion changes as the calculation of + * `last` proceeds outward through the AST; for example, a `BreakCompletion` is + * caught up by its surrounding loop and turned into a `NormalCompletion`, and a + * `NormalCompletion` proceeds outward through the end of a `finally` block and is + * turned into whatever completion was caught by the `finally`. + * + * An important goal of the CFG is to get the order of side-effects correct. + * Most expressions can have side-effects and must therefore be modeled in the + * CFG in AST post-order. For example, a `MethodCall` evaluates its arguments + * before the call. Most statements do not have side-effects, but merely affect + * the control flow and some could therefore be excluded from the CFG. However, + * as a design choice, all statements are included in the CFG and generally + * serve as their own entry-points, thus executing in some version of AST + * pre-order. */ - private abstract class StandardElement extends ControlFlowElement { - /** Gets the first child element of this element. */ - ControlFlowElement getFirstChildElement() { - result = this.getChildElement(0) + private module Successor { + /** + * A control flow element where the children are evaluated following a + * standard left-to-right evaluation. The actual evaluation order is + * determined by the predicate `getChildElement()`. + */ + private abstract class StandardElement extends ControlFlowElement { + /** Gets the first child element of this element. */ + ControlFlowElement getFirstChildElement() { + result = this.getChildElement(0) + } + + /** Holds if this element has no children. */ + predicate isLeafElement() { + not exists(this.getFirstChildElement()) + } + + /** Gets the last child element of this element. */ + ControlFlowElement getLastChildElement() { + exists(int last | + last = max(int i | exists(this.getChildElement(i))) and + result = this.getChildElement(last) + ) + } + + /** Gets the `i`th child element, which is not the last element. */ + ControlFlowElement getNonLastChildElement(int i) { + result = this.getChildElement(i) and + not result = this.getLastChildElement() + } + + /** Gets the `i`th child element, in order of evaluation, starting from 0. */ + abstract ControlFlowElement getChildElement(int i); + } + + private class StandardStmt extends StandardElement, Stmt { + StandardStmt() { + // The following statements need special treatment + not this instanceof IfStmt and + not this instanceof SwitchStmt and + not this instanceof ConstCase and + not this instanceof TypeCase and + not this instanceof LoopStmt and + not this instanceof TryStmt and + not this instanceof SpecificCatchClause and + not this instanceof JumpStmt + } + + override ControlFlowElement getChildElement(int i) { + not this instanceof FixedStmt and + not this instanceof UsingStmt and + result = this.getChild(i) + or + this = any(GeneralCatchClause gcc | + i = 0 and result = gcc.getBlock() + ) + or + this = any(FixedStmt fs | + result = fs.getVariableDeclExpr(i) + or + result = fs.getBody() and + i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1 + ) + or + this = any(UsingStmt us | + if exists(us.getExpr()) then ( + result = us.getExpr() and + i = 0 + or + result = us.getBody() and + i = 1 + ) else ( + result = us.getVariableDeclExpr(i) + or + result = us.getBody() and + i = max(int j | exists(us.getVariableDeclExpr(j))) + 1 + ) + ) + } + } + + /** + * An assignment operation that has an expanded version. We use the expanded + * version in the control flow graph in order to get better data flow / taint + * tracking. + */ + private class AssignOperationWithExpandedAssignment extends AssignOperation { + AssignOperationWithExpandedAssignment() { + this.hasExpandedAssignment() + } } - /** Holds if this element has no children. */ - predicate isLeafElement() { - not exists(this.getFirstChildElement()) + /** A conditionally qualified expression. */ + private class ConditionalQualifiableExpr extends QualifiableExpr { + ConditionalQualifiableExpr() { + this.isConditional() + } } - /** Gets the last child element of this element. */ - ControlFlowElement getLastChildElement() { - exists(int last | - last = max(int i | exists(this.getChildElement(i))) and - result = this.getChildElement(last) - ) + private class StandardExpr extends StandardElement, Expr { + StandardExpr() { + // The following expressions need special treatment + not this instanceof LogicalNotExpr and + not this instanceof LogicalAndExpr and + not this instanceof LogicalOrExpr and + not this instanceof NullCoalescingExpr and + not this instanceof ConditionalExpr and + not this instanceof AssignOperationWithExpandedAssignment and + not this instanceof ConditionalQualifiableExpr and + not this instanceof ThrowExpr and + not this instanceof TypeAccess and + not this instanceof ObjectCreation and + not this instanceof ArrayCreation + } + + override ControlFlowElement getChildElement(int i) { + not this instanceof TypeofExpr and + not this instanceof DefaultValueExpr and + not this instanceof SizeofExpr and + not this instanceof NameOfExpr and + not this instanceof QualifiableExpr and + not this instanceof Assignment and + not this instanceof IsExpr and + not this instanceof AsExpr and + not this instanceof CastExpr and + not this instanceof AnonymousFunctionExpr and + not this instanceof DelegateCall and + result = this.getChild(i) + or + this = any(ExtensionMethodCall emc | + result = emc.getArgument(i) + ) + or + result = getQualifiableExprChild(this, i) + or + result = getAssignmentChild(this, i) + or + result = getIsExprChild(this, i) + or + result = getAsExprChild(this, i) + or + result = getCastExprChild(this, i) + or + result = this.(DelegateCall).getChild(i - 1) + } } - /** Gets the `i`th child element, which is not the last element. */ - ControlFlowElement getNonLastChildElement(int i) { - result = this.getChildElement(i) and - not result = this.getLastChildElement() + private ControlFlowElement getQualifiableExprChild(QualifiableExpr qe, int i) { + i >= 0 and + not qe instanceof ExtensionMethodCall and + not qe.isConditional() and + if exists(Expr q | q = qe.getQualifier() | not q instanceof TypeAccess) then + result = qe.getChild(i - 1) + else + result = qe.getChild(i) } - /** Gets the `i`th child element, in order of evaluation, starting from 0. */ - abstract ControlFlowElement getChildElement(int i); - } + private ControlFlowElement getAssignmentChild(Assignment a, int i) { + // The left-hand side of an assignment is evaluated before the right-hand side + i = 0 and result = a.getLValue() + or + i = 1 and result = a.getRValue() + } - private class StandardStmt extends StandardElement, Stmt { - StandardStmt() { - // The following statements need special treatment - not this instanceof IfStmt and - not this instanceof SwitchStmt and - not this instanceof ConstCase and - not this instanceof TypeCase and - not this instanceof LoopStmt and - not this instanceof TryStmt and - not this instanceof SpecificCatchClause and - not this instanceof JumpStmt - } - - override ControlFlowElement getChildElement(int i) { - not this instanceof FixedStmt and - not this instanceof UsingStmt and - result = this.getChild(i) - or - this = any(GeneralCatchClause gcc | - i = 0 and result = gcc.getBlock() - ) + private ControlFlowElement getIsExprChild(IsExpr ie, int i) { + // The type access at index 1 is not evaluated at run-time + i = 0 and result = ie.getExpr() or - this = any(FixedStmt fs | - result = fs.getVariableDeclExpr(i) - or - result = fs.getBody() and - i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1 - ) + i = 1 and result = ie.(IsPatternExpr).getVariableDeclExpr() or - this = any(UsingStmt us | - if exists(us.getExpr()) then ( - result = us.getExpr() and - i = 0 - or - result = us.getBody() and - i = 1 - ) else ( - result = us.getVariableDeclExpr(i) - or - result = us.getBody() and - i = max(int j | exists(us.getVariableDeclExpr(j))) + 1 - ) - ) + i = 1 and result = ie.(IsConstantExpr).getConstant() } - } - /** - * An assignment operation that has an expanded version. We use the expanded - * version in the control flow graph in order to get better data flow / taint - * tracking. - */ - private class AssignOperationWithExpandedAssignment extends AssignOperation { - AssignOperationWithExpandedAssignment() { - this.hasExpandedAssignment() + private ControlFlowElement getAsExprChild(AsExpr ae, int i) { + // The type access at index 1 is not evaluated at run-time + i = 0 and result = ae.getExpr() } - } - /** A conditionally qualified expression. */ - private class ConditionalQualifiableExpr extends QualifiableExpr { - ConditionalQualifiableExpr() { - this.isConditional() + private ControlFlowElement getCastExprChild(CastExpr ce, int i) { + // The type access at index 1 is not evaluated at run-time + i = 0 and result = ce.getExpr() } - } - private class StandardExpr extends StandardElement, Expr { - StandardExpr() { - // The following expressions need special treatment - not this instanceof LogicalNotExpr and - not this instanceof LogicalAndExpr and - not this instanceof LogicalOrExpr and - not this instanceof NullCoalescingExpr and - not this instanceof ConditionalExpr and - not this instanceof AssignOperationWithExpandedAssignment and - not this instanceof ConditionalQualifiableExpr and - not this instanceof ThrowExpr and - not this instanceof TypeAccess and - not this instanceof ObjectCreation and - not this instanceof ArrayCreation - } - - override ControlFlowElement getChildElement(int i) { - not this instanceof TypeofExpr and - not this instanceof DefaultValueExpr and - not this instanceof SizeofExpr and - not this instanceof NameOfExpr and - not this instanceof QualifiableExpr and - not this instanceof Assignment and - not this instanceof IsExpr and - not this instanceof AsExpr and - not this instanceof CastExpr and - not this instanceof AnonymousFunctionExpr and - not this instanceof DelegateCall and - result = this.getChild(i) - or - this = any(ExtensionMethodCall emc | - result = emc.getArgument(i) + /** + * Gets the first element executed within control flow element `cfe`. + */ + ControlFlowElement first(ControlFlowElement cfe) { + // Pre-order: element itself + cfe instanceof PreOrderElement and + result = cfe + or + // Post-order: first element of first child (or self, if no children) + cfe = any(PostOrderElement poe | + result = first(poe.getFirstChild()) + or + not exists(poe.getFirstChild()) and + result = poe ) or - result = getQualifiableExprChild(this, i) - or - result = getAssignmentChild(this, i) - or - result = getIsExprChild(this, i) + cfe = any(AssignOperationWithExpandedAssignment a | + result = first(a.getExpandedAssignment()) + ) or - result = getAsExprChild(this, i) + cfe = any(ConditionalQualifiableExpr cqe | + result = first(cqe.getChildExpr(-1)) + ) or - result = getCastExprChild(this, i) + cfe = any(ArrayCreation ac | + if ac.isImplicitlySized() then + // No length argument: element itself + result = ac + else + // First element of first length argument + result = first(ac.getLengthArgument(0)) + ) or - result = this.(DelegateCall).getChild(i - 1) + cfe = any(ForeachStmt fs | + // Unlike most other statements, `foreach` statements are not modelled in + // pre-order, because we use the `foreach` node itself to represent the + // emptiness test that determines whether to execute the loop body + result = first(fs.getIterableExpr()) + ) } - } - - private ControlFlowElement getQualifiableExprChild(QualifiableExpr qe, int i) { - i >= 0 and - not qe instanceof ExtensionMethodCall and - not qe.isConditional() and - if exists(Expr q | q = qe.getQualifier() | not q instanceof TypeAccess) then - result = qe.getChild(i - 1) - else - result = qe.getChild(i) - } - - private ControlFlowElement getAssignmentChild(Assignment a, int i) { - // The left-hand side of an assignment is evaluated before the right-hand side - i = 0 and result = a.getLValue() - or - i = 1 and result = a.getRValue() - } - - private ControlFlowElement getIsExprChild(IsExpr ie, int i) { - // The type access at index 1 is not evaluated at run-time - i = 0 and result = ie.getExpr() - or - i = 1 and result = ie.(IsPatternExpr).getVariableDeclExpr() - or - i = 1 and result = ie.(IsConstantExpr).getConstant() - } - - private ControlFlowElement getAsExprChild(AsExpr ae, int i) { - // The type access at index 1 is not evaluated at run-time - i = 0 and result = ae.getExpr() - } - - private ControlFlowElement getCastExprChild(CastExpr ce, int i) { - // The type access at index 1 is not evaluated at run-time - i = 0 and result = ce.getExpr() - } - - /** - * Gets the first element executed within control flow element `cfe`. - */ - ControlFlowElement first(ControlFlowElement cfe) { - // Pre-order: element itself - cfe instanceof PreOrderElement and - result = cfe - or - // Post-order: first element of first child (or self, if no children) - cfe = any(PostOrderElement poe | - result = first(poe.getFirstChild()) - or - not exists(poe.getFirstChild()) and - result = poe - ) - or - cfe = any(AssignOperationWithExpandedAssignment a | - result = first(a.getExpandedAssignment()) - ) - or - cfe = any(ConditionalQualifiableExpr cqe | - result = first(cqe.getChildExpr(-1)) - ) - or - cfe = any(ArrayCreation ac | - if ac.isImplicitlySized() then - // No length argument: element itself - result = ac - else - // First element of first length argument - result = first(ac.getLengthArgument(0)) - ) - or - cfe = any(ForeachStmt fs | - // Unlike most other statements, `foreach` statements are not modelled in - // pre-order, because we use the `foreach` node itself to represent the - // emptiness test that determines whether to execute the loop body - result = first(fs.getIterableExpr()) - ) - } - private class PreOrderElement extends ControlFlowElement { - PreOrderElement() { - this instanceof StandardStmt or - this instanceof IfStmt or - this instanceof SwitchStmt or - this instanceof ConstCase or - this instanceof TypeCase or - this instanceof TryStmt or - this instanceof SpecificCatchClause or - (this instanceof LoopStmt and not this instanceof ForeachStmt) or - this instanceof LogicalNotExpr or - this instanceof LogicalAndExpr or - this instanceof LogicalOrExpr or - this instanceof NullCoalescingExpr or - this instanceof ConditionalExpr + private class PreOrderElement extends ControlFlowElement { + PreOrderElement() { + this instanceof StandardStmt or + this instanceof IfStmt or + this instanceof SwitchStmt or + this instanceof ConstCase or + this instanceof TypeCase or + this instanceof TryStmt or + this instanceof SpecificCatchClause or + (this instanceof LoopStmt and not this instanceof ForeachStmt) or + this instanceof LogicalNotExpr or + this instanceof LogicalAndExpr or + this instanceof LogicalOrExpr or + this instanceof NullCoalescingExpr or + this instanceof ConditionalExpr + } } - } - private class PostOrderElement extends ControlFlowElement { - PostOrderElement() { - this instanceof StandardExpr or - this instanceof JumpStmt or - this instanceof ThrowExpr or - this instanceof ObjectCreation - } + private class PostOrderElement extends ControlFlowElement { + PostOrderElement() { + this instanceof StandardExpr or + this instanceof JumpStmt or + this instanceof ThrowExpr or + this instanceof ObjectCreation + } - ControlFlowElement getFirstChild() { - result = this.(StandardExpr).getFirstChildElement() or - result = this.(JumpStmt).getChild(0) or - result = this.(ThrowExpr).getExpr() or - result = this.(ObjectCreation).getArgument(0) + ControlFlowElement getFirstChild() { + result = this.(StandardExpr).getFirstChildElement() or + result = this.(JumpStmt).getChild(0) or + result = this.(ThrowExpr).getExpr() or + result = this.(ObjectCreation).getArgument(0) + } } - } - /** - * Gets a potential last element executed within control flow element `cfe`, - * as well as its completion. - * - * For example, if `cfe` is `A || B` then both `A` and `B` are potential - * last elements with Boolean completions. - */ - ControlFlowElement last(ControlFlowElement cfe, Completion c) { - // Pre-order: last element of last child (or self, if no children) - cfe = any(StandardStmt ss | - result = last(ss.getLastChildElement(), c) - or - ss.isLeafElement() and - result = ss and - c.isValidFor(result) - ) - or - // Post-order: element itself - cfe instanceof StandardExpr and - not cfe instanceof NonReturningCall and - result = cfe and - c.isValidFor(result) - or - // Pre/post order: a child exits abnormally - result = last(cfe.(StandardElement).getChildElement(_), c) and - not c instanceof NormalCompletion - or - cfe = any(LogicalNotExpr lne | - // Operand exits with a Boolean completion - exists(BooleanCompletion operandCompletion | - result = lastLogicalNotExprOperand(lne, operandCompletion) | - c = any(BooleanCompletion bc | - bc.getOuterValue() = operandCompletion.getOuterValue().booleanNot() and - bc.getInnerValue() = operandCompletion.getInnerValue() - ) + /** + * Gets a potential last element executed within control flow element `cfe`, + * as well as its completion. + * + * For example, if `cfe` is `A || B` then both `A` and `B` are potential + * last elements with Boolean completions. + */ + ControlFlowElement last(ControlFlowElement cfe, Completion c) { + // Pre-order: last element of last child (or self, if no children) + cfe = any(StandardStmt ss | + result = last(ss.getLastChildElement(), c) + or + ss.isLeafElement() and + result = ss and + c.isValidFor(result) ) or - // Operand exits with a non-Boolean completion - result = lastLogicalNotExprOperand(lne, c) and - not c instanceof BooleanCompletion - ) - or - cfe = any(LogicalAndExpr lae | - // Left operand exits with a false completion - result = lastLogicalAndExprLeftOperand(lae, c) and - c instanceof FalseCompletion - or - // Left operand exits abnormally - result = lastLogicalAndExprLeftOperand(lae, c) and - not c instanceof NormalCompletion - or - // Right operand exits with any completion - result = lastLogicalAndExprRightOperand(lae, c) - ) - or - cfe = any(LogicalOrExpr loe | - // Left operand exits with a true completion - result = lastLogicalOrExprLeftOperand(loe, c) and - c instanceof TrueCompletion - or - // Left operand exits abnormally - result = lastLogicalOrExprLeftOperand(loe, c) and - not c instanceof NormalCompletion - or - // Right operand exits with any completion - result = lastLogicalOrExprRightOperand(loe, c) - ) - or - cfe = any(NullCoalescingExpr nce | - // Left operand exits with any non-`null` completion - result = lastNullCoalescingExprLeftOperand(nce, c) and - not c.(NullnessCompletion).isNull() - or - // Right operand exits with any completion - result = lastNullCoalescingExprRightOperand(nce, c) - ) - or - cfe = any(ConditionalExpr ce | - // Condition exits abnormally - result = lastConditionalExprCondition(ce, c) and - not c instanceof NormalCompletion - or - // Then branch exits with any completion - result = lastConditionalExprThen(ce, c) - or - // Else branch exits with any completion - result = lastConditionalExprElse(ce, c) - ) - or - result = lastAssignOperationWithExpandedAssignmentExpandedAssignment(cfe, c) - or - cfe = any(ConditionalQualifiableExpr cqe | - // Post-order: element itself - result = cqe and - c.isValidFor(cqe) - or - // Qualifier exits with a `null` completion - result = lastConditionalQualifiableExprChildExpr(cqe, -1, c) and - c.(NullnessCompletion).isNull() - ) - or - cfe = any(ThrowExpr te | // Post-order: element itself - te.getThrownExceptionType() = c.(ThrowCompletion).getExceptionClass() and - result = te - or - // Expression being thrown exits abnormally - result = lastThrowExprExpr(te, c) and - not c instanceof NormalCompletion - ) - or - cfe = any(ObjectCreation oc | - // Post-order: element itself (when no initializer) - result = oc and - not oc.hasInitializer() and - c.isValidFor(result) - or - // Last element of initializer - result = lastObjectCreationInitializer(oc, c) - ) - or - cfe = any(ArrayCreation ac | - // Post-order: element itself (when no initializer) - result = ac and - not ac.hasInitializer() and + cfe instanceof StandardExpr and + not cfe instanceof NonReturningCall and + result = cfe and c.isValidFor(result) or - // Last element of initializer - result = lastArrayCreationInitializer(ac, c) - ) - or - cfe = any(IfStmt is | - // Condition exits with a false completion and there is no `else` branch - result = lastIfStmtCondition(is, c) and - c instanceof FalseCompletion and - not exists(is.getElse()) - or - // Condition exits abnormally - result = lastIfStmtCondition(is, c) and + // Pre/post order: a child exits abnormally + result = last(cfe.(StandardElement).getChildElement(_), c) and not c instanceof NormalCompletion or - // Then branch exits with any completion - result = lastIfStmtThen(is, c) - or - // Else branch exits with any completion - result = lastIfStmtElse(is, c) - ) - or - cfe = any(SwitchStmt ss | - // Switch expression exits normally and there are no cases - result = lastSwitchStmtCondition(ss, c) and - not exists(ss.getACase()) and - c instanceof NormalCompletion - or - // Switch expression exits abnormally - result = lastSwitchStmtCondition(ss, c) and - not c instanceof NormalCompletion + cfe = any(LogicalNotExpr lne | + // Operand exits with a Boolean completion + exists(BooleanCompletion operandCompletion | + result = lastLogicalNotExprOperand(lne, operandCompletion) | + c = any(BooleanCompletion bc | + bc.getOuterValue() = operandCompletion.getOuterValue().booleanNot() and + bc.getInnerValue() = operandCompletion.getInnerValue() + ) + ) + or + // Operand exits with a non-Boolean completion + result = lastLogicalNotExprOperand(lne, c) and + not c instanceof BooleanCompletion + ) or - // A statement exits with a `break` completion - result = lastSwitchStmtStmt(ss, _, any(BreakCompletion bc)) and - c instanceof BreakNormalCompletion + cfe = any(LogicalAndExpr lae | + // Left operand exits with a false completion + result = lastLogicalAndExprLeftOperand(lae, c) and + c instanceof FalseCompletion + or + // Left operand exits abnormally + result = lastLogicalAndExprLeftOperand(lae, c) and + not c instanceof NormalCompletion + or + // Right operand exits with any completion + result = lastLogicalAndExprRightOperand(lae, c) + ) or - // A statement exits abnormally - result = lastSwitchStmtStmt(ss, _, c) and - not c instanceof BreakCompletion and - not c instanceof NormalCompletion and - not c instanceof GotoDefaultCompletion and - not c instanceof GotoCaseCompletion + cfe = any(LogicalOrExpr loe | + // Left operand exits with a true completion + result = lastLogicalOrExprLeftOperand(loe, c) and + c instanceof TrueCompletion + or + // Left operand exits abnormally + result = lastLogicalOrExprLeftOperand(loe, c) and + not c instanceof NormalCompletion + or + // Right operand exits with any completion + result = lastLogicalOrExprRightOperand(loe, c) + ) or - // Last case exits with a non-match - exists(int last | - last = max(int i | exists(ss.getCase(i))) | - result = lastConstCaseNoMatch(ss.getCase(last), c) or - result = lastTypeCaseNoMatch(ss.getCase(last), c) + cfe = any(NullCoalescingExpr nce | + // Left operand exits with any non-`null` completion + result = lastNullCoalescingExprLeftOperand(nce, c) and + not c.(NullnessCompletion).isNull() + or + // Right operand exits with any completion + result = lastNullCoalescingExprRightOperand(nce, c) ) or - // Last statement exits with any non-break completion - exists(int last | - last = max(int i | exists(ss.getStmt(i))) | - result = lastSwitchStmtStmt(ss, last, c) and - not c instanceof BreakCompletion + cfe = any(ConditionalExpr ce | + // Condition exits abnormally + result = lastConditionalExprCondition(ce, c) and + not c instanceof NormalCompletion + or + // Then branch exits with any completion + result = lastConditionalExprThen(ce, c) + or + // Else branch exits with any completion + result = lastConditionalExprElse(ce, c) ) - ) - or - cfe = any(ConstCase cc | - // Case expression exits with a non-match - result = lastConstCaseNoMatch(cc, c) or - // Case expression exits abnormally - result = lastConstCaseExpr(cc, c) and - not c instanceof NormalCompletion + result = lastAssignOperationWithExpandedAssignmentExpandedAssignment(cfe, c) or - // Case statement exits with any completion - result = lastConstCaseStmt(cc, c) - ) - or - cfe = any(TypeCase tc | - // Type test exits with a non-match - result = lastTypeCaseNoMatch(tc, c) + cfe = any(ConditionalQualifiableExpr cqe | + // Post-order: element itself + result = cqe and + c.isValidFor(cqe) + or + // Qualifier exits with a `null` completion + result = lastConditionalQualifiableExprChildExpr(cqe, -1, c) and + c.(NullnessCompletion).isNull() + ) or - // Condition exists with a `false` completion - result = lastTypeCaseCondition(tc, c) and - c instanceof FalseCompletion + cfe = any(ThrowExpr te | + // Post-order: element itself + te.getThrownExceptionType() = c.(ThrowCompletion).getExceptionClass() and + result = te + or + // Expression being thrown exits abnormally + result = lastThrowExprExpr(te, c) and + not c instanceof NormalCompletion + ) or - // Condition exists abnormally - result = lastTypeCaseCondition(tc, c) and - not c instanceof NormalCompletion + cfe = any(ObjectCreation oc | + // Post-order: element itself (when no initializer) + result = oc and + not oc.hasInitializer() and + c.isValidFor(result) + or + // Last element of initializer + result = lastObjectCreationInitializer(oc, c) + ) or - // Case statement exits with any completion - result = lastTypeCaseStmt(tc, c) - ) - or - exists(LoopStmt ls | - cfe = ls and - not ls instanceof ForeachStmt | - // Condition exits with a false completion - result = lastLoopStmtCondition(ls, c) and - c instanceof FalseCompletion - or - // Condition exits abnormally - result = lastLoopStmtCondition(ls, c) and - not c instanceof NormalCompletion + cfe = any(ArrayCreation ac | + // Post-order: element itself (when no initializer) + result = ac and + not ac.hasInitializer() and + c.isValidFor(result) + or + // Last element of initializer + result = lastArrayCreationInitializer(ac, c) + ) or - exists(Completion bodyCompletion | - result = lastLoopStmtBody(ls, bodyCompletion) | - if bodyCompletion instanceof BreakCompletion then - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - c instanceof BreakNormalCompletion - else ( - // Body exits with a completion that does not continue the loop - not bodyCompletion.continuesLoop() and - c = bodyCompletion - ) + cfe = any(IfStmt is | + // Condition exits with a false completion and there is no `else` branch + result = lastIfStmtCondition(is, c) and + c instanceof FalseCompletion and + not exists(is.getElse()) + or + // Condition exits abnormally + result = lastIfStmtCondition(is, c) and + not c instanceof NormalCompletion + or + // Then branch exits with any completion + result = lastIfStmtThen(is, c) + or + // Else branch exits with any completion + result = lastIfStmtElse(is, c) ) - ) - or - cfe = any(ForeachStmt fs | - // Iterator expression exits abnormally - result = lastForeachStmtIterableExpr(fs, c) and - not c instanceof NormalCompletion or - // Emptiness test exits with no more elements - result = fs and - c.(EmptinessCompletion).isEmpty() - or - exists(Completion bodyCompletion | - result = lastLoopStmtBody(fs, bodyCompletion) | - if bodyCompletion instanceof BreakCompletion then - // Body exits with a break completion; the loop exits normally - // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` - // in order to be able to get the correct break label in the control flow - // graph from the `result` node to the node after the loop. - c instanceof BreakNormalCompletion - else ( - // Body exits abnormally - c = bodyCompletion and - not c instanceof NormalCompletion and - not c instanceof ContinueCompletion + cfe = any(SwitchStmt ss | + // Switch expression exits normally and there are no cases + result = lastSwitchStmtCondition(ss, c) and + not exists(ss.getACase()) and + c instanceof NormalCompletion + or + // Switch expression exits abnormally + result = lastSwitchStmtCondition(ss, c) and + not c instanceof NormalCompletion + or + // A statement exits with a `break` completion + result = lastSwitchStmtStmt(ss, _, any(BreakCompletion bc)) and + c instanceof BreakNormalCompletion + or + // A statement exits abnormally + result = lastSwitchStmtStmt(ss, _, c) and + not c instanceof BreakCompletion and + not c instanceof NormalCompletion and + not c instanceof GotoDefaultCompletion and + not c instanceof GotoCaseCompletion + or + // Last case exits with a non-match + exists(int last | + last = max(int i | exists(ss.getCase(i))) | + result = lastConstCaseNoMatch(ss.getCase(last), c) or + result = lastTypeCaseNoMatch(ss.getCase(last), c) + ) + or + // Last statement exits with any non-break completion + exists(int last | + last = max(int i | exists(ss.getStmt(i))) | + result = lastSwitchStmtStmt(ss, last, c) and + not c instanceof BreakCompletion ) ) - ) - or - cfe = any(TryStmt ts | - // If the `finally` block completes normally, it resumes any non-normal - // completion that was current before the `finally` block was entered - exists(Completion finallyCompletion | - result = lastTryStmtFinally(ts, finallyCompletion) and - finallyCompletion instanceof NormalCompletion - | - exists(getBlockOrCatchFinallyPred(ts, any(NormalCompletion nc))) and - c = finallyCompletion + or + cfe = any(ConstCase cc | + // Case expression exits with a non-match + result = lastConstCaseNoMatch(cc, c) or - exists(getBlockOrCatchFinallyPred(ts, c)) and + // Case expression exits abnormally + result = lastConstCaseExpr(cc, c) and not c instanceof NormalCompletion + or + // Case statement exits with any completion + result = lastConstCaseStmt(cc, c) ) or - // If the `finally` block completes abnormally, take the completion of - // the `finally` block itself - result = lastTryStmtFinally(ts, c) and - not c instanceof NormalCompletion - or - // If there is no `finally` block, last elements are from the body, from - // the blocks of one of the `catch` clauses, or from the last `catch` clause - not ts.hasFinally() and - result = getBlockOrCatchFinallyPred(ts, c) - ) - or - cfe = any(SpecificCatchClause scc | - // Last element of `catch` block - result = lastCatchClauseBlock(cfe, c) + cfe = any(TypeCase tc | + // Type test exits with a non-match + result = lastTypeCaseNoMatch(tc, c) + or + // Condition exists with a `false` completion + result = lastTypeCaseCondition(tc, c) and + c instanceof FalseCompletion + or + // Condition exists abnormally + result = lastTypeCaseCondition(tc, c) and + not c instanceof NormalCompletion + or + // Case statement exits with any completion + result = lastTypeCaseStmt(tc, c) + ) or - ( - if scc.isLast() then ( - // Last `catch` clause inherits throw completions from the `try` block, - // when the clause does not match - throwMayBeUncaught(scc, c) and - ( - // Incompatible exception type: clause itself - result = scc - or - // Incompatible filter - result = lastSpecificCatchClauseFilterClause(scc, _) + exists(LoopStmt ls | + cfe = ls and + not ls instanceof ForeachStmt | + // Condition exits with a false completion + result = lastLoopStmtCondition(ls, c) and + c instanceof FalseCompletion + or + // Condition exits abnormally + result = lastLoopStmtCondition(ls, c) and + not c instanceof NormalCompletion + or + exists(Completion bodyCompletion | + result = lastLoopStmtBody(ls, bodyCompletion) | + if bodyCompletion instanceof BreakCompletion then + // Body exits with a break completion; the loop exits normally + // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` + // in order to be able to get the correct break label in the control flow + // graph from the `result` node to the node after the loop. + c instanceof BreakNormalCompletion + else ( + // Body exits with a completion that does not continue the loop + not bodyCompletion.continuesLoop() and + c = bodyCompletion ) - ) else ( - // Incompatible exception type: clause itself - result = scc and - c = any(MatchingCompletion mc | not mc.isMatch()) - or - // Incompatible filter - result = lastSpecificCatchClauseFilterClause(scc, c) and - c instanceof FalseCompletion ) ) - ) - or - cfe = any(JumpStmt js | - // Post-order: element itself - result = js and - ( - js instanceof BreakStmt and c instanceof BreakCompletion - or - js instanceof ContinueStmt and c instanceof ContinueCompletion - or - js = c.(GotoLabelCompletion).getGotoStmt() - or - js = c.(GotoCaseCompletion).getGotoStmt() + or + cfe = any(ForeachStmt fs | + // Iterator expression exits abnormally + result = lastForeachStmtIterableExpr(fs, c) and + not c instanceof NormalCompletion or - js instanceof GotoDefaultStmt and c instanceof GotoDefaultCompletion + // Emptiness test exits with no more elements + result = fs and + c.(EmptinessCompletion).isEmpty() or - js.(ThrowStmt).getThrownExceptionType() = c.(ThrowCompletion).getExceptionClass() + exists(Completion bodyCompletion | + result = lastLoopStmtBody(fs, bodyCompletion) | + if bodyCompletion instanceof BreakCompletion then + // Body exits with a break completion; the loop exits normally + // Note: we use a `BreakNormalCompletion` rather than a `NormalCompletion` + // in order to be able to get the correct break label in the control flow + // graph from the `result` node to the node after the loop. + c instanceof BreakNormalCompletion + else ( + // Body exits abnormally + c = bodyCompletion and + not c instanceof NormalCompletion and + not c instanceof ContinueCompletion + ) + ) + ) + or + cfe = any(TryStmt ts | + // If the `finally` block completes normally, it resumes any non-normal + // completion that was current before the `finally` block was entered + exists(Completion finallyCompletion | + result = lastTryStmtFinally(ts, finallyCompletion) and + finallyCompletion instanceof NormalCompletion + | + exists(getBlockOrCatchFinallyPred(ts, any(NormalCompletion nc))) and + c = finallyCompletion + or + exists(getBlockOrCatchFinallyPred(ts, c)) and + not c instanceof NormalCompletion + ) or - js instanceof ReturnStmt and c instanceof ReturnCompletion + // If the `finally` block completes abnormally, take the completion of + // the `finally` block itself + result = lastTryStmtFinally(ts, c) and + not c instanceof NormalCompletion or - // `yield break` behaves like a return statement - js instanceof YieldBreakStmt and c instanceof ReturnCompletion + // If there is no `finally` block, last elements are from the body, from + // the blocks of one of the `catch` clauses, or from the last `catch` clause + not ts.hasFinally() and + result = getBlockOrCatchFinallyPred(ts, c) + ) + or + cfe = any(SpecificCatchClause scc | + // Last element of `catch` block + result = lastCatchClauseBlock(cfe, c) or - // `yield return` behaves like a normal statement - js instanceof YieldReturnStmt and c.isValidFor(js) + ( + if scc.isLast() then ( + // Last `catch` clause inherits throw completions from the `try` block, + // when the clause does not match + throwMayBeUncaught(scc, c) and + ( + // Incompatible exception type: clause itself + result = scc + or + // Incompatible filter + result = lastSpecificCatchClauseFilterClause(scc, _) + ) + ) else ( + // Incompatible exception type: clause itself + result = scc and + c = any(MatchingCompletion mc | not mc.isMatch()) + or + // Incompatible filter + result = lastSpecificCatchClauseFilterClause(scc, c) and + c instanceof FalseCompletion + ) + ) ) or - // Child exits abnormally - result = lastJumpStmtChild(cfe, c) and - not c instanceof NormalCompletion - ) - or - // Propagate completion from a call to a non-terminating callable - cfe = any(NonReturningCall nrc | - result = nrc and - c = nrc.getTarget().(NonReturningCallable).getACallCompletion() - ) - } - - private ControlFlowElement lastConstCaseNoMatch(ConstCase cc, MatchingCompletion c) { - result = lastConstCaseExpr(cc, c) and - not c.isMatch() - } + cfe = any(JumpStmt js | + // Post-order: element itself + result = js and + ( + js instanceof BreakStmt and c instanceof BreakCompletion + or + js instanceof ContinueStmt and c instanceof ContinueCompletion + or + js = c.(GotoLabelCompletion).getGotoStmt() + or + js = c.(GotoCaseCompletion).getGotoStmt() + or + js instanceof GotoDefaultStmt and c instanceof GotoDefaultCompletion + or + js.(ThrowStmt).getThrownExceptionType() = c.(ThrowCompletion).getExceptionClass() + or + js instanceof ReturnStmt and c instanceof ReturnCompletion + or + // `yield break` behaves like a return statement + js instanceof YieldBreakStmt and c instanceof ReturnCompletion + or + // `yield return` behaves like a normal statement + js instanceof YieldReturnStmt and c.isValidFor(js) + ) + or + // Child exits abnormally + result = lastJumpStmtChild(cfe, c) and + not c instanceof NormalCompletion + ) + or + // Propagate completion from a call to a non-terminating callable + cfe = any(NonReturningCall nrc | + result = nrc and + c = nrc.getTarget().(NonReturningCallable).getACallCompletion() + ) + } - private ControlFlowElement lastTypeCaseNoMatch(TypeCase tc, MatchingCompletion c) { - result = tc.getTypeAccess() and - not c.isMatch() and - c.isValidFor(result) - } + private ControlFlowElement lastConstCaseNoMatch(ConstCase cc, MatchingCompletion c) { + result = lastConstCaseExpr(cc, c) and + not c.isMatch() + } - pragma [nomagic] - private ControlFlowElement lastStandardElementGetNonLastChildElement(StandardElement se, int i, Completion c) { - result = last(se.getNonLastChildElement(i), c) - } + private ControlFlowElement lastTypeCaseNoMatch(TypeCase tc, MatchingCompletion c) { + result = tc.getTypeAccess() and + not c.isMatch() and + c.isValidFor(result) + } - pragma [nomagic] - private ControlFlowElement lastThrowExprExpr(ThrowExpr te, Completion c) { - result = last(te.getExpr(), c) - } + pragma [nomagic] + private ControlFlowElement lastStandardElementGetNonLastChildElement(StandardElement se, int i, Completion c) { + result = last(se.getNonLastChildElement(i), c) + } - pragma [nomagic] - private ControlFlowElement lastLogicalNotExprOperand(LogicalNotExpr lne, Completion c) { - result = last(lne.getOperand(), c) - } + pragma [nomagic] + private ControlFlowElement lastThrowExprExpr(ThrowExpr te, Completion c) { + result = last(te.getExpr(), c) + } - pragma [nomagic] - private ControlFlowElement lastLogicalAndExprLeftOperand(LogicalAndExpr lae, Completion c) { - result = last(lae.getLeftOperand(), c) - } + pragma [nomagic] + private ControlFlowElement lastLogicalNotExprOperand(LogicalNotExpr lne, Completion c) { + result = last(lne.getOperand(), c) + } - pragma [nomagic] - private ControlFlowElement lastLogicalAndExprRightOperand(LogicalAndExpr lae, Completion c) { - result = last(lae.getRightOperand(), c) - } + pragma [nomagic] + private ControlFlowElement lastLogicalAndExprLeftOperand(LogicalAndExpr lae, Completion c) { + result = last(lae.getLeftOperand(), c) + } - pragma [nomagic] - private ControlFlowElement lastLogicalOrExprLeftOperand(LogicalOrExpr loe, Completion c) { - result = last(loe.getLeftOperand(), c) - } + pragma [nomagic] + private ControlFlowElement lastLogicalAndExprRightOperand(LogicalAndExpr lae, Completion c) { + result = last(lae.getRightOperand(), c) + } - pragma [nomagic] - private ControlFlowElement lastLogicalOrExprRightOperand(LogicalOrExpr loe, Completion c) { - result = last(loe.getRightOperand(), c) - } + pragma [nomagic] + private ControlFlowElement lastLogicalOrExprLeftOperand(LogicalOrExpr loe, Completion c) { + result = last(loe.getLeftOperand(), c) + } - pragma [nomagic] - private ControlFlowElement lastNullCoalescingExprLeftOperand(NullCoalescingExpr nce, Completion c) { - result = last(nce.getLeftOperand(), c) - } + pragma [nomagic] + private ControlFlowElement lastLogicalOrExprRightOperand(LogicalOrExpr loe, Completion c) { + result = last(loe.getRightOperand(), c) + } - pragma [nomagic] - private ControlFlowElement lastNullCoalescingExprRightOperand(NullCoalescingExpr nce, Completion c) { - result = last(nce.getRightOperand(), c) - } + pragma [nomagic] + private ControlFlowElement lastNullCoalescingExprLeftOperand(NullCoalescingExpr nce, Completion c) { + result = last(nce.getLeftOperand(), c) + } - pragma [nomagic] - private ControlFlowElement lastConditionalExprCondition(ConditionalExpr ce, Completion c) { - result = last(ce.getCondition(), c) - } + pragma [nomagic] + private ControlFlowElement lastNullCoalescingExprRightOperand(NullCoalescingExpr nce, Completion c) { + result = last(nce.getRightOperand(), c) + } - pragma [nomagic] - private ControlFlowElement lastConditionalExprThen(ConditionalExpr ce, Completion c) { - result = last(ce.getThen(), c) - } + pragma [nomagic] + private ControlFlowElement lastConditionalExprCondition(ConditionalExpr ce, Completion c) { + result = last(ce.getCondition(), c) + } - pragma [nomagic] - private ControlFlowElement lastConditionalExprElse(ConditionalExpr ce, Completion c) { - result = last(ce.getElse(), c) - } + pragma [nomagic] + private ControlFlowElement lastConditionalExprThen(ConditionalExpr ce, Completion c) { + result = last(ce.getThen(), c) + } - pragma [nomagic] - private ControlFlowElement lastAssignOperationWithExpandedAssignmentExpandedAssignment(AssignOperationWithExpandedAssignment a, Completion c) { - result = last(a.getExpandedAssignment(), c) - } + pragma [nomagic] + private ControlFlowElement lastConditionalExprElse(ConditionalExpr ce, Completion c) { + result = last(ce.getElse(), c) + } - pragma [nomagic] - private ControlFlowElement lastConditionalQualifiableExprChildExpr(ConditionalQualifiableExpr cqe, int i, Completion c) { - result = last(cqe.getChildExpr(i), c) - } + pragma [nomagic] + private ControlFlowElement lastAssignOperationWithExpandedAssignmentExpandedAssignment(AssignOperationWithExpandedAssignment a, Completion c) { + result = last(a.getExpandedAssignment(), c) + } - pragma [nomagic] - private ControlFlowElement lastObjectCreationArgument(ObjectCreation oc, int i, Completion c) { - result = last(oc.getArgument(i), c) - } + pragma [nomagic] + private ControlFlowElement lastConditionalQualifiableExprChildExpr(ConditionalQualifiableExpr cqe, int i, Completion c) { + result = last(cqe.getChildExpr(i), c) + } - pragma [nomagic] - private ControlFlowElement lastObjectCreationInitializer(ObjectCreation oc, Completion c) { - result = last(oc.getInitializer(), c) - } + pragma [nomagic] + private ControlFlowElement lastObjectCreationArgument(ObjectCreation oc, int i, Completion c) { + result = last(oc.getArgument(i), c) + } - pragma [nomagic] - private ControlFlowElement lastArrayCreationInitializer(ArrayCreation ac, Completion c) { - result = last(ac.getInitializer(), c) - } + pragma [nomagic] + private ControlFlowElement lastObjectCreationInitializer(ObjectCreation oc, Completion c) { + result = last(oc.getInitializer(), c) + } - pragma [nomagic] - private ControlFlowElement lastArrayCreationLengthArgument(ArrayCreation ac, int i, Completion c) { - not ac.isImplicitlySized() and - result = last(ac.getLengthArgument(i), c) - } + pragma [nomagic] + private ControlFlowElement lastArrayCreationInitializer(ArrayCreation ac, Completion c) { + result = last(ac.getInitializer(), c) + } - pragma [nomagic] - private ControlFlowElement lastIfStmtCondition(IfStmt is, Completion c) { - result = last(is.getCondition(), c) - } + pragma [nomagic] + private ControlFlowElement lastArrayCreationLengthArgument(ArrayCreation ac, int i, Completion c) { + not ac.isImplicitlySized() and + result = last(ac.getLengthArgument(i), c) + } - pragma [nomagic] - private ControlFlowElement lastIfStmtThen(IfStmt is, Completion c) { - result = last(is.getThen(), c) - } + pragma [nomagic] + private ControlFlowElement lastIfStmtCondition(IfStmt is, Completion c) { + result = last(is.getCondition(), c) + } - pragma [nomagic] - private ControlFlowElement lastIfStmtElse(IfStmt is, Completion c) { - result = last(is.getElse(), c) - } + pragma [nomagic] + private ControlFlowElement lastIfStmtThen(IfStmt is, Completion c) { + result = last(is.getThen(), c) + } - pragma [nomagic] - private ControlFlowElement lastSwitchStmtCondition(SwitchStmt ss, Completion c) { - result = last(ss.getCondition(), c) - } + pragma [nomagic] + private ControlFlowElement lastIfStmtElse(IfStmt is, Completion c) { + result = last(is.getElse(), c) + } - pragma [nomagic] - private ControlFlowElement lastSwitchStmtStmt(SwitchStmt ss, int i, Completion c) { - result = last(ss.getStmt(i), c) - } + pragma [nomagic] + private ControlFlowElement lastSwitchStmtCondition(SwitchStmt ss, Completion c) { + result = last(ss.getCondition(), c) + } - pragma [nomagic] - private ControlFlowElement lastSwitchStmtCaseStmt(SwitchStmt ss, int i, Completion c) { - result = last(ss.getStmt(i).(ConstCase).getStmt(), c) or - result = last(ss.getStmt(i).(TypeCase).getStmt(), c) or - result = last(ss.getStmt(i).(DefaultCase), c) - } + pragma [nomagic] + private ControlFlowElement lastSwitchStmtStmt(SwitchStmt ss, int i, Completion c) { + result = last(ss.getStmt(i), c) + } - pragma [nomagic] - private ControlFlowElement lastConstCaseExpr(ConstCase cc, Completion c) { - result = last(cc.getExpr(), c) - } + pragma [nomagic] + private ControlFlowElement lastSwitchStmtCaseStmt(SwitchStmt ss, int i, Completion c) { + result = last(ss.getStmt(i).(ConstCase).getStmt(), c) or + result = last(ss.getStmt(i).(TypeCase).getStmt(), c) or + result = last(ss.getStmt(i).(DefaultCase), c) + } - pragma [nomagic] - private ControlFlowElement lastConstCaseStmt(ConstCase cc, Completion c) { - result = last(cc.getStmt(), c) - } + pragma [nomagic] + private ControlFlowElement lastConstCaseExpr(ConstCase cc, Completion c) { + result = last(cc.getExpr(), c) + } - pragma [nomagic] - private ControlFlowElement lastTypeCaseCondition(TypeCase tc, Completion c) { - result = last(tc.getCondition(), c) - } + pragma [nomagic] + private ControlFlowElement lastConstCaseStmt(ConstCase cc, Completion c) { + result = last(cc.getStmt(), c) + } - pragma [nomagic] - private ControlFlowElement lastTypeCaseVariableDeclExpr(TypeCase tc, Completion c) { - result = last(tc.getVariableDeclExpr(), c) - } + pragma [nomagic] + private ControlFlowElement lastTypeCaseCondition(TypeCase tc, Completion c) { + result = last(tc.getCondition(), c) + } - pragma [nomagic] - private ControlFlowElement lastTypeCaseStmt(TypeCase tc, Completion c) { - result = last(tc.getStmt(), c) - } + pragma [nomagic] + private ControlFlowElement lastTypeCaseVariableDeclExpr(TypeCase tc, Completion c) { + result = last(tc.getVariableDeclExpr(), c) + } - pragma [nomagic] - private ControlFlowElement lastLoopStmtCondition(LoopStmt ls, Completion c) { - result = last(ls.getCondition(), c) - } + pragma [nomagic] + private ControlFlowElement lastTypeCaseStmt(TypeCase tc, Completion c) { + result = last(tc.getStmt(), c) + } - pragma [nomagic] - private ControlFlowElement lastLoopStmtBody(LoopStmt ls, Completion c) { - result = last(ls.getBody(), c) - } + pragma [nomagic] + private ControlFlowElement lastLoopStmtCondition(LoopStmt ls, Completion c) { + result = last(ls.getCondition(), c) + } - pragma [nomagic] - private ControlFlowElement lastForeachStmtIterableExpr(ForeachStmt fs, Completion c) { - result = last(fs.getIterableExpr(), c) - } + pragma [nomagic] + private ControlFlowElement lastLoopStmtBody(LoopStmt ls, Completion c) { + result = last(ls.getBody(), c) + } - pragma [nomagic] - private ControlFlowElement lastForeachStmtVariableDeclExpr(ForeachStmt fs, Completion c) { - result = last(fs.getVariableDeclExpr(), c) - } + pragma [nomagic] + private ControlFlowElement lastForeachStmtIterableExpr(ForeachStmt fs, Completion c) { + result = last(fs.getIterableExpr(), c) + } - pragma [nomagic] - private ControlFlowElement lastJumpStmtChild(JumpStmt js, Completion c) { - result = last(js.getChild(0), c) - } + pragma [nomagic] + private ControlFlowElement lastForeachStmtVariableDeclExpr(ForeachStmt fs, Completion c) { + result = last(fs.getVariableDeclExpr(), c) + } - pragma [nomagic] - ControlFlowElement lastTryStmtFinally(TryStmt ts, Completion c) { - result = last(ts.getFinally(), c) - } + pragma [nomagic] + private ControlFlowElement lastJumpStmtChild(JumpStmt js, Completion c) { + result = last(js.getChild(0), c) + } - pragma [nomagic] - private ControlFlowElement lastTryStmtBlock(TryStmt ts, Completion c) { - result = last(ts.getBlock(), c) - } + pragma [nomagic] + ControlFlowElement lastTryStmtFinally(TryStmt ts, Completion c) { + result = last(ts.getFinally(), c) + } - pragma [nomagic] - ControlFlowElement lastTryStmtCatchClause(TryStmt ts, int i, Completion c) { - result = last(ts.getCatchClause(i), c) - } + pragma [nomagic] + private ControlFlowElement lastTryStmtBlock(TryStmt ts, Completion c) { + result = last(ts.getBlock(), c) + } - pragma [nomagic] - private ControlFlowElement lastSpecificCatchClauseFilterClause(SpecificCatchClause scc, Completion c) { - result = last(scc.getFilterClause(), c) - } + pragma [nomagic] + ControlFlowElement lastTryStmtCatchClause(TryStmt ts, int i, Completion c) { + result = last(ts.getCatchClause(i), c) + } - pragma [nomagic] - private ControlFlowElement lastCatchClauseBlock(CatchClause cc, Completion c) { - result = last(cc.getBlock(), c) - } + pragma [nomagic] + private ControlFlowElement lastSpecificCatchClauseFilterClause(SpecificCatchClause scc, Completion c) { + result = last(scc.getFilterClause(), c) + } - pragma [nomagic] - private ControlFlowElement lastStandardExprLastChildElement(StandardExpr se, Completion c) { - result = last(se.getLastChildElement(), c) - } + pragma [nomagic] + private ControlFlowElement lastCatchClauseBlock(CatchClause cc, Completion c) { + result = last(cc.getBlock(), c) + } - pragma [nomagic] - private ControlFlowElement lastForStmtUpdate(ForStmt fs, int i, Completion c) { - result = last(fs.getUpdate(i), c) - } + pragma [nomagic] + private ControlFlowElement lastStandardExprLastChildElement(StandardExpr se, Completion c) { + result = last(se.getLastChildElement(), c) + } - pragma [nomagic] - private ControlFlowElement lastForStmtInitializer(ForStmt fs, int i, Completion c) { - result = last(fs.getInitializer(i), c) - } + pragma [nomagic] + private ControlFlowElement lastForStmtUpdate(ForStmt fs, int i, Completion c) { + result = last(fs.getUpdate(i), c) + } - /** - * Gets a last element from a `try` or `catch` block of this `try` statement - * that may finish with completion `c`, such that control may be transferred - * to the `finally` block (if it exists). - */ - pragma [nomagic] - private ControlFlowElement getBlockOrCatchFinallyPred(TryStmt ts, Completion c) { - result = lastTryStmtBlock(ts, c) and - ( - // Any non-throw completion from the `try` block will always continue directly - // to the `finally` block - not c instanceof ThrowCompletion - or - // Any completion from the `try` block will continue to the `finally` block - // when there are no catch clauses - not exists(ts.getACatchClause()) - ) - or - // Last element from any of the `catch` clause blocks continues to the `finally` block - result = lastCatchClauseBlock(ts.getACatchClause(), c) - or - // Last element of last `catch` clause continues to the `finally` block - exists(int last | - ts.getCatchClause(last).isLast() | - result = lastTryStmtCatchClause(ts, last, c) - ) - } + pragma [nomagic] + private ControlFlowElement lastForStmtInitializer(ForStmt fs, int i, Completion c) { + result = last(fs.getInitializer(i), c) + } - /** - * Holds if the `try` block that catch clause `scc` belongs to may throw an - * exception of type `c`, where no `catch` clause is guaranteed to catch it. - * The catch clause `last` is the last catch clause in the `try` statement - * that it belongs to. - */ - pragma [nomagic] - private predicate throwMayBeUncaught(SpecificCatchClause last, ThrowCompletion c) { - exists(TryStmt ts | - ts = last.getTryStmt() and - exists(lastTryStmtBlock(ts, c)) and - not ts.getACatchClause() instanceof GeneralCatchClause and - forall(SpecificCatchClause scc | - scc = ts.getACatchClause() | - scc.hasFilterClause() - or - not c.getExceptionClass().getABaseType*() = scc.getCaughtExceptionType() - ) and - last.isLast() - ) - } + /** + * Gets a last element from a `try` or `catch` block of this `try` statement + * that may finish with completion `c`, such that control may be transferred + * to the `finally` block (if it exists). + */ + pragma [nomagic] + private ControlFlowElement getBlockOrCatchFinallyPred(TryStmt ts, Completion c) { + result = lastTryStmtBlock(ts, c) and + ( + // Any non-throw completion from the `try` block will always continue directly + // to the `finally` block + not c instanceof ThrowCompletion + or + // Any completion from the `try` block will continue to the `finally` block + // when there are no catch clauses + not exists(ts.getACatchClause()) + ) + or + // Last element from any of the `catch` clause blocks continues to the `finally` block + result = lastCatchClauseBlock(ts.getACatchClause(), c) + or + // Last element of last `catch` clause continues to the `finally` block + exists(int last | + ts.getCatchClause(last).isLast() | + result = lastTryStmtCatchClause(ts, last, c) + ) + } - /** - * Provides a simple analysis for identifying calls to callables that will - * not return. - */ - private module NonReturning { - private import semmle.code.csharp.ExprOrStmtParent - private import semmle.code.csharp.frameworks.System + /** + * Holds if the `try` block that catch clause `scc` belongs to may throw an + * exception of type `c`, where no `catch` clause is guaranteed to catch it. + * The catch clause `last` is the last catch clause in the `try` statement + * that it belongs to. + */ + pragma [nomagic] + private predicate throwMayBeUncaught(SpecificCatchClause last, ThrowCompletion c) { + exists(TryStmt ts | + ts = last.getTryStmt() and + exists(lastTryStmtBlock(ts, c)) and + not ts.getACatchClause() instanceof GeneralCatchClause and + forall(SpecificCatchClause scc | + scc = ts.getACatchClause() | + scc.hasFilterClause() + or + not c.getExceptionClass().getABaseType*() = scc.getCaughtExceptionType() + ) and + last.isLast() + ) + } /** - * A call that definitely does not return (conservative analysis). + * Provides a simple analysis for identifying calls to callables that will + * not return. */ - class NonReturningCall extends Call { - NonReturningCall() { - this.getTarget() instanceof NonReturningCallable + private module NonReturning { + private import semmle.code.csharp.ExprOrStmtParent + private import semmle.code.csharp.frameworks.System + + /** + * A call that definitely does not return (conservative analysis). + */ + class NonReturningCall extends Call { + NonReturningCall() { + this.getTarget() instanceof NonReturningCallable + } } - } - /** A callable that does not return. */ - abstract class NonReturningCallable extends Callable { - NonReturningCallable() { - not exists(ReturnStmt ret | ret.getEnclosingCallable() = this) and - not hasAccessorAutoImplementation(this, _) and - not exists(Virtualizable v | - v.isOverridableOrImplementable() | - v = this or - v = this.(Accessor).getDeclaration() - ) + /** A callable that does not return. */ + abstract class NonReturningCallable extends Callable { + NonReturningCallable() { + not exists(ReturnStmt ret | ret.getEnclosingCallable() = this) and + not hasAccessorAutoImplementation(this, _) and + not exists(Virtualizable v | + v.isOverridableOrImplementable() | + v = this or + v = this.(Accessor).getDeclaration() + ) + } + + /** Gets a valid completion for a call to this non-returning callable. */ + abstract Completion getACallCompletion(); } - /** Gets a valid completion for a call to this non-returning callable. */ - abstract Completion getACallCompletion(); - } + /** + * A callable that exits when called. + */ + private abstract class ExitingCallable extends NonReturningCallable { + override Completion getACallCompletion() { + result instanceof ReturnCompletion + } + } - /** - * A callable that exits when called. - */ - private abstract class ExitingCallable extends NonReturningCallable { - override Completion getACallCompletion() { - result instanceof ReturnCompletion + private class DirectlyExitingCallable extends ExitingCallable { + DirectlyExitingCallable() { + this = any(Method m | + m.hasQualifiedName("System.Environment", "Exit") or + m.hasQualifiedName("System.Windows.Forms.Application", "Exit") + ) + } } - } - private class DirectlyExitingCallable extends ExitingCallable { - DirectlyExitingCallable() { - this = any(Method m | - m.hasQualifiedName("System.Environment", "Exit") or - m.hasQualifiedName("System.Windows.Forms.Application", "Exit") - ) + private class IndirectlyExitingCallable extends ExitingCallable { + IndirectlyExitingCallable() { + forex(ControlFlowElement body | + body = this.getABody() | + body = getAnExitingElement() + ) + } + } + + private ControlFlowElement getAnExitingElement() { + result.(Call).getTarget() instanceof ExitingCallable or + result = getAnExitingStmt() } - } - private class IndirectlyExitingCallable extends ExitingCallable { - IndirectlyExitingCallable() { - forex(ControlFlowElement body | - body = this.getABody() | - body = getAnExitingElement() + private Stmt getAnExitingStmt() { + result.(ExprStmt).getExpr() = getAnExitingElement() + or + result.(BlockStmt).getFirstStmt() = getAnExitingElement() + or + exists(IfStmt ifStmt | + result = ifStmt and + ifStmt.getThen() = getAnExitingElement() and + ifStmt.getElse() = getAnExitingElement() ) } - } - private ControlFlowElement getAnExitingElement() { - result.(Call).getTarget() instanceof ExitingCallable or - result = getAnExitingStmt() - } + /** + * A callable that throws an exception when called. + */ + private class ThrowingCallable extends NonReturningCallable { + ThrowingCallable() { + forex(ControlFlowElement body | + body = this.getABody() | + body = getAThrowingElement(_) + ) + } - private Stmt getAnExitingStmt() { - result.(ExprStmt).getExpr() = getAnExitingElement() - or - result.(BlockStmt).getFirstStmt() = getAnExitingElement() - or - exists(IfStmt ifStmt | - result = ifStmt and - ifStmt.getThen() = getAnExitingElement() and - ifStmt.getElse() = getAnExitingElement() - ) - } + override ThrowCompletion getACallCompletion() { + this.getABody() = getAThrowingElement(result) + } + } - /** - * A callable that throws an exception when called. - */ - private class ThrowingCallable extends NonReturningCallable { - ThrowingCallable() { - forex(ControlFlowElement body | - body = this.getABody() | - body = getAThrowingElement(_) + private ControlFlowElement getAThrowingElement(ThrowCompletion c) { + c = result.(Call).getTarget().(ThrowingCallable).getACallCompletion() + or + result = any(ThrowElement te | + c.(ThrowCompletion).getExceptionClass() = te.getThrownExceptionType() and + // For stub implementations, there may exist proper implementations that are not seen + // during compilation, so we conservatively rule those out + not isStub(te) + ) + or + result = getAThrowingStmt(c) + } + + private Stmt getAThrowingStmt(ThrowCompletion c) { + result.(ExprStmt).getExpr() = getAThrowingElement(c) + or + result.(BlockStmt).getFirstStmt() = getAThrowingStmt(c) + or + exists(IfStmt ifStmt | + result = ifStmt and + ifStmt.getThen() = getAThrowingElement(_) and + ifStmt.getElse() = getAThrowingElement(_) | + ifStmt.getThen() = getAThrowingElement(c) or + ifStmt.getElse() = getAThrowingElement(c) ) } - override ThrowCompletion getACallCompletion() { - this.getABody() = getAThrowingElement(result) + /** Holds if `throw` element `te` indicates a stub implementation. */ + private predicate isStub(ThrowElement te) { + exists(Expr e | + e = te.getExpr() | + e instanceof NullLiteral or + e.getType() instanceof SystemNotImplementedExceptionClass + ) } } + private import NonReturning - private ControlFlowElement getAThrowingElement(ThrowCompletion c) { - c = result.(Call).getTarget().(ThrowingCallable).getACallCompletion() - or - result = any(ThrowElement te | - c.(ThrowCompletion).getExceptionClass() = te.getThrownExceptionType() and - // For stub implementations, there may exist proper implementations that are not seen - // during compilation, so we conservatively rule those out - not isStub(te) + /** + * Gets a control flow successor for control flow element `cfe`, given that + * `cfe` finishes with completion `c`. + */ + pragma [nomagic] + ControlFlowElement succ(ControlFlowElement cfe, Completion c) { + // Pre-order: flow from element itself to first element of first child + cfe = any(StandardStmt ss | + result = first(ss.getFirstChildElement()) and + c instanceof SimpleCompletion ) or - result = getAThrowingStmt(c) - } - - private Stmt getAThrowingStmt(ThrowCompletion c) { - result.(ExprStmt).getExpr() = getAThrowingElement(c) - or - result.(BlockStmt).getFirstStmt() = getAThrowingStmt(c) + // Post-order: flow from last element of last child to element itself + cfe = lastStandardExprLastChildElement(result, c) and + c instanceof NormalCompletion or - exists(IfStmt ifStmt | - result = ifStmt and - ifStmt.getThen() = getAThrowingElement(_) and - ifStmt.getElse() = getAThrowingElement(_) | - ifStmt.getThen() = getAThrowingElement(c) or - ifStmt.getElse() = getAThrowingElement(c) + // Standard left-to-right evaluation + exists(StandardElement parent, int i | + cfe = lastStandardElementGetNonLastChildElement(parent, i, c) and + c instanceof NormalCompletion and + result = first(parent.getChildElement(i + 1)) ) - } - - /** Holds if `throw` element `te` indicates a stub implementation. */ - private predicate isStub(ThrowElement te) { - exists(Expr e | - e = te.getExpr() | - e instanceof NullLiteral or - e.getType() instanceof SystemNotImplementedExceptionClass + or + cfe = any(LogicalNotExpr lne | + // Pre-order: flow from expression itself to first element of operand + result = first(lne.getOperand()) and + c instanceof SimpleCompletion ) - } - } - private import NonReturning - - /** - * Gets a control flow successor for control flow element `cfe`, given that - * `cfe` finishes with completion `c`. - */ - pragma [nomagic] - ControlFlowElement succ(ControlFlowElement cfe, Completion c) { - // Pre-order: flow from element itself to first element of first child - cfe = any(StandardStmt ss | - result = first(ss.getFirstChildElement()) and - c instanceof SimpleCompletion - ) - or - // Post-order: flow from last element of last child to element itself - cfe = lastStandardExprLastChildElement(result, c) and - c instanceof NormalCompletion - or - // Standard left-to-right evaluation - exists(StandardElement parent, int i | - cfe = lastStandardElementGetNonLastChildElement(parent, i, c) and - c instanceof NormalCompletion and - result = first(parent.getChildElement(i + 1)) - ) - or - cfe = any(LogicalNotExpr lne | - // Pre-order: flow from expression itself to first element of operand - result = first(lne.getOperand()) and - c instanceof SimpleCompletion - ) - or - exists(LogicalAndExpr lae | - // Pre-order: flow from expression itself to first element of left operand - lae = cfe and - result = first(lae.getLeftOperand()) and - c instanceof SimpleCompletion - or - // Flow from last element of left operand to first element of right operand - cfe = lastLogicalAndExprLeftOperand(lae, c) and - c instanceof TrueCompletion and - result = first(lae.getRightOperand()) - ) - or - exists(LogicalOrExpr loe | - // Pre-order: flow from expression itself to first element of left operand - loe = cfe and - result = first(loe.getLeftOperand()) and - c instanceof SimpleCompletion - or - // Flow from last element of left operand to first element of right operand - cfe = lastLogicalOrExprLeftOperand(loe, c) and - c instanceof FalseCompletion and - result = first(loe.getRightOperand()) - ) - or - exists(NullCoalescingExpr nce | - // Pre-order: flow from expression itself to first element of left operand - nce = cfe and - result = first(nce.getLeftOperand()) and - c instanceof SimpleCompletion - or - // Flow from last element of left operand to first element of right operand - cfe = lastNullCoalescingExprLeftOperand(nce, c) and - c.(NullnessCompletion).isNull() and - result = first(nce.getRightOperand()) - ) - or - exists(ConditionalExpr ce | - // Pre-order: flow from expression itself to first element of condition - ce = cfe and - result = first(ce.getCondition()) and - c instanceof SimpleCompletion - or - // Flow from last element of condition to first element of then branch - cfe = lastConditionalExprCondition(ce, c) and - c instanceof TrueCompletion and - result = first(ce.getThen()) - or - // Flow from last element of condition to first element of else branch - cfe = lastConditionalExprCondition(ce, c) and - c instanceof FalseCompletion and - result = first(ce.getElse()) - ) - or - exists(ConditionalQualifiableExpr parent, int i | - cfe = lastConditionalQualifiableExprChildExpr(parent, i, c) and - c instanceof NormalCompletion and - not c.(NullnessCompletion).isNull() - | - // Post-order: flow from last element of last child to element itself - i = max(int j | exists(parent.getChildExpr(j))) and - result = parent or - // Standard left-to-right evaluation - result = first(parent.getChildExpr(i + 1)) - ) - or - // Post-order: flow from last element of thrown expression to expression itself - cfe = lastThrowExprExpr(result, c) and - c instanceof NormalCompletion - or - exists(ObjectCreation oc | - // Flow from last element of argument `i` to first element of argument `i+1` - exists(int i | - cfe = lastObjectCreationArgument(oc, i, c) | - result = first(oc.getArgument(i + 1)) and - c instanceof NormalCompletion + exists(LogicalAndExpr lae | + // Pre-order: flow from expression itself to first element of left operand + lae = cfe and + result = first(lae.getLeftOperand()) and + c instanceof SimpleCompletion + or + // Flow from last element of left operand to first element of right operand + cfe = lastLogicalAndExprLeftOperand(lae, c) and + c instanceof TrueCompletion and + result = first(lae.getRightOperand()) ) or - // Flow from last element of last argument to self - exists(int last | - last = max(int i | exists(oc.getArgument(i))) | - cfe = lastObjectCreationArgument(oc, last, c) and - result = oc and - c instanceof NormalCompletion + exists(LogicalOrExpr loe | + // Pre-order: flow from expression itself to first element of left operand + loe = cfe and + result = first(loe.getLeftOperand()) and + c instanceof SimpleCompletion + or + // Flow from last element of left operand to first element of right operand + cfe = lastLogicalOrExprLeftOperand(loe, c) and + c instanceof FalseCompletion and + result = first(loe.getRightOperand()) ) or - // Flow from self to first element of initializer - cfe = oc and - result = first(oc.getInitializer()) and - c instanceof SimpleCompletion - ) - or - exists(ArrayCreation ac | - // Flow from self to first element of initializer - cfe = ac and - result = first(ac.getInitializer()) and - c instanceof SimpleCompletion - or - exists(int i | - cfe = lastArrayCreationLengthArgument(ac, i, c) and - c instanceof SimpleCompletion | - // Flow from last length argument to self - i = max(int j | exists(ac.getLengthArgument(j))) and - result = ac - or - // Flow from one length argument to the next - result = first(ac.getLengthArgument(i + 1)) + exists(NullCoalescingExpr nce | + // Pre-order: flow from expression itself to first element of left operand + nce = cfe and + result = first(nce.getLeftOperand()) and + c instanceof SimpleCompletion + or + // Flow from last element of left operand to first element of right operand + cfe = lastNullCoalescingExprLeftOperand(nce, c) and + c.(NullnessCompletion).isNull() and + result = first(nce.getRightOperand()) ) - ) - or - exists(IfStmt is | - // Pre-order: flow from statement itself to first element of condition - cfe = is and - result = first(is.getCondition()) and - c instanceof SimpleCompletion or - cfe = lastIfStmtCondition(is, c) - and - ( + exists(ConditionalExpr ce | + // Pre-order: flow from expression itself to first element of condition + ce = cfe and + result = first(ce.getCondition()) and + c instanceof SimpleCompletion + or // Flow from last element of condition to first element of then branch - c instanceof TrueCompletion and result = first(is.getThen()) + cfe = lastConditionalExprCondition(ce, c) and + c instanceof TrueCompletion and + result = first(ce.getThen()) or // Flow from last element of condition to first element of else branch - c instanceof FalseCompletion and result = first(is.getElse()) + cfe = lastConditionalExprCondition(ce, c) and + c instanceof FalseCompletion and + result = first(ce.getElse()) ) - ) - or - exists(SwitchStmt ss | - // Pre-order: flow from statement itself to first element of switch expression - cfe = ss and - result = first(ss.getCondition()) and - c instanceof SimpleCompletion - or - // Flow from last element of switch expression to first element of first statement - cfe = lastSwitchStmtCondition(ss, c) and - c instanceof NormalCompletion and - result = first(ss.getStmt(0)) or - // Flow from last element of non-`case` statement `i` to first element of statement `i+1` - exists(int i | - cfe = lastSwitchStmtStmt(ss, i, c) | - not ss.getStmt(i) instanceof CaseStmt and + exists(ConditionalQualifiableExpr parent, int i | + cfe = lastConditionalQualifiableExprChildExpr(parent, i, c) and c instanceof NormalCompletion and - result = first(ss.getStmt(i + 1)) + not c.(NullnessCompletion).isNull() + | + // Post-order: flow from last element of last child to element itself + i = max(int j | exists(parent.getChildExpr(j))) and + result = parent + or + // Standard left-to-right evaluation + result = first(parent.getChildExpr(i + 1)) + ) + or + // Post-order: flow from last element of thrown expression to expression itself + cfe = lastThrowExprExpr(result, c) and + c instanceof NormalCompletion + or + exists(ObjectCreation oc | + // Flow from last element of argument `i` to first element of argument `i+1` + exists(int i | + cfe = lastObjectCreationArgument(oc, i, c) | + result = first(oc.getArgument(i + 1)) and + c instanceof NormalCompletion + ) + or + // Flow from last element of last argument to self + exists(int last | + last = max(int i | exists(oc.getArgument(i))) | + cfe = lastObjectCreationArgument(oc, last, c) and + result = oc and + c instanceof NormalCompletion + ) + or + // Flow from self to first element of initializer + cfe = oc and + result = first(oc.getInitializer()) and + c instanceof SimpleCompletion + ) + or + exists(ArrayCreation ac | + // Flow from self to first element of initializer + cfe = ac and + result = first(ac.getInitializer()) and + c instanceof SimpleCompletion + or + exists(int i | + cfe = lastArrayCreationLengthArgument(ac, i, c) and + c instanceof SimpleCompletion | + // Flow from last length argument to self + i = max(int j | exists(ac.getLengthArgument(j))) and + result = ac + or + // Flow from one length argument to the next + result = first(ac.getLengthArgument(i + 1)) + ) ) or - // Flow from last element of `case` statement `i` to first element of statement `i+1` - exists(int i | - cfe = lastSwitchStmtCaseStmt(ss, i, c) | + exists(IfStmt is | + // Pre-order: flow from statement itself to first element of condition + cfe = is and + result = first(is.getCondition()) and + c instanceof SimpleCompletion + or + cfe = lastIfStmtCondition(is, c) + and + ( + // Flow from last element of condition to first element of then branch + c instanceof TrueCompletion and result = first(is.getThen()) + or + // Flow from last element of condition to first element of else branch + c instanceof FalseCompletion and result = first(is.getElse()) + ) + ) + or + exists(SwitchStmt ss | + // Pre-order: flow from statement itself to first element of switch expression + cfe = ss and + result = first(ss.getCondition()) and + c instanceof SimpleCompletion + or + // Flow from last element of switch expression to first element of first statement + cfe = lastSwitchStmtCondition(ss, c) and c instanceof NormalCompletion and - result = first(ss.getStmt(i + 1)) + result = first(ss.getStmt(0)) + or + // Flow from last element of non-`case` statement `i` to first element of statement `i+1` + exists(int i | + cfe = lastSwitchStmtStmt(ss, i, c) | + not ss.getStmt(i) instanceof CaseStmt and + c instanceof NormalCompletion and + result = first(ss.getStmt(i + 1)) + ) + or + // Flow from last element of `case` statement `i` to first element of statement `i+1` + exists(int i | + cfe = lastSwitchStmtCaseStmt(ss, i, c) | + c instanceof NormalCompletion and + result = first(ss.getStmt(i + 1)) + ) + or + // Flow from last element of case expression to next case + exists(ConstCase cc, int i | + cc = ss.getCase(i) | + cfe = lastConstCaseExpr(cc, c) and + c = any(MatchingCompletion mc | not mc.isMatch()) and + result = first(ss.getCase(i + 1)) + ) + or + // Flow from last element of condition to next case + exists(TypeCase tc, int i | + tc = ss.getCase(i) | + cfe = lastTypeCaseCondition(tc, c) and + c instanceof FalseCompletion and + result = first(ss.getCase(i + 1)) + ) + or + exists(GotoCompletion gc | + cfe = lastSwitchStmtStmt(ss, _, gc) and + gc = c | + // Flow from last element of a statement with a `goto default` completion + // to first element `default` statement + gc instanceof GotoDefaultCompletion and + result = first(ss.getDefaultCase()) + or + // Flow from last element of a statement with a `goto case` completion + // to first element of relevant case + exists(ConstCase cc | + cc = ss.getAConstCase() and + cc.getLabel() = gc.(GotoCaseCompletion).getLabel() and + result = first(cc.getStmt()) + ) + ) ) or - // Flow from last element of case expression to next case - exists(ConstCase cc, int i | - cc = ss.getCase(i) | + exists(ConstCase cc | + // Pre-order: flow from statement itself to first element of expression + cfe = cc and + result = first(cc.getExpr()) and + c instanceof SimpleCompletion + or + // Flow from last element of case expression to first element of statement cfe = lastConstCaseExpr(cc, c) and - c = any(MatchingCompletion mc | not mc.isMatch()) and - result = first(ss.getCase(i + 1)) - ) - or - // Flow from last element of condition to next case - exists(TypeCase tc, int i | - tc = ss.getCase(i) | - cfe = lastTypeCaseCondition(tc, c) and - c instanceof FalseCompletion and - result = first(ss.getCase(i + 1)) + c.(MatchingCompletion).isMatch() and + result = first(cc.getStmt()) ) or - exists(GotoCompletion gc | - cfe = lastSwitchStmtStmt(ss, _, gc) and - gc = c | - // Flow from last element of a statement with a `goto default` completion - // to first element `default` statement - gc instanceof GotoDefaultCompletion and - result = first(ss.getDefaultCase()) - or - // Flow from last element of a statement with a `goto case` completion - // to first element of relevant case - exists(ConstCase cc | - cc = ss.getAConstCase() and - cc.getLabel() = gc.(GotoCaseCompletion).getLabel() and - result = first(cc.getStmt()) - ) - ) - ) - or - exists(ConstCase cc | - // Pre-order: flow from statement itself to first element of expression - cfe = cc and - result = first(cc.getExpr()) and - c instanceof SimpleCompletion - or - // Flow from last element of case expression to first element of statement - cfe = lastConstCaseExpr(cc, c) and - c.(MatchingCompletion).isMatch() and - result = first(cc.getStmt()) - ) - or - exists(TypeCase tc | - // Pre-order: flow from statement itself to type test - cfe = tc and - result = tc.getTypeAccess() and - c instanceof SimpleCompletion - or - cfe = tc.getTypeAccess() and - c.isValidFor(cfe) and - c = any(MatchingCompletion mc | - if mc.isMatch() then - if exists(tc.getVariableDeclExpr()) then - // Flow from type test to first element of variable declaration - result = first(tc.getVariableDeclExpr()) - else if exists(tc.getCondition()) then - // Flow from type test to first element of condition - result = first(tc.getCondition()) + exists(TypeCase tc | + // Pre-order: flow from statement itself to type test + cfe = tc and + result = tc.getTypeAccess() and + c instanceof SimpleCompletion + or + cfe = tc.getTypeAccess() and + c.isValidFor(cfe) and + c = any(MatchingCompletion mc | + if mc.isMatch() then + if exists(tc.getVariableDeclExpr()) then + // Flow from type test to first element of variable declaration + result = first(tc.getVariableDeclExpr()) + else if exists(tc.getCondition()) then + // Flow from type test to first element of condition + result = first(tc.getCondition()) + else + // Flow from type test to first element of statement + result = first(tc.getStmt()) else - // Flow from type test to first element of statement - result = first(tc.getStmt()) + // Flow from type test to first element of next case + exists(SwitchStmt ss, int i | + tc = ss.getCase(i) | + result = first(ss.getCase(i + 1)) + ) + ) + or + cfe = lastTypeCaseVariableDeclExpr(tc, c) and + if exists(tc.getCondition()) then + // Flow from variable declaration to first element of condition + result = first(tc.getCondition()) else - // Flow from type test to first element of next case - exists(SwitchStmt ss, int i | - tc = ss.getCase(i) | - result = first(ss.getCase(i + 1)) - ) - ) - or - cfe = lastTypeCaseVariableDeclExpr(tc, c) and - if exists(tc.getCondition()) then - // Flow from variable declaration to first element of condition - result = first(tc.getCondition()) - else - // Flow from variable declaration to first element of statement + // Flow from variable declaration to first element of statement + result = first(tc.getStmt()) + or + // Flow from condition to first element of statement + cfe = lastTypeCaseCondition(tc, c) and + c instanceof TrueCompletion and result = first(tc.getStmt()) + ) or - // Flow from condition to first element of statement - cfe = lastTypeCaseCondition(tc, c) and - c instanceof TrueCompletion and - result = first(tc.getStmt()) - ) - or - exists(LoopStmt ls | - // Flow from last element of condition to first element of loop body - cfe = lastLoopStmtCondition(ls, c) and - c instanceof TrueCompletion and - result = first(ls.getBody()) - or - // Flow from last element of loop body back to first element of condition - not ls instanceof ForStmt and - cfe = lastLoopStmtBody(ls, c) and - c.continuesLoop() and - result = first(ls.getCondition()) - ) - or - cfe = any(WhileStmt ws | - // Pre-order: flow from statement itself to first element of condition - result = first(ws.getCondition()) and - c instanceof SimpleCompletion - ) - or - cfe = any(DoStmt ds | - // Pre-order: flow from statement itself to first element of body - result = first(ds.getBody()) and - c instanceof SimpleCompletion - ) - or - exists(ForStmt fs | - // Pre-order: flow from statement itself to first element of first initializer/ - // condition/loop body - exists(ControlFlowElement next | - cfe = fs and - result = first(next) and - c instanceof SimpleCompletion | - next = fs.getInitializer(0) + exists(LoopStmt ls | + // Flow from last element of condition to first element of loop body + cfe = lastLoopStmtCondition(ls, c) and + c instanceof TrueCompletion and + result = first(ls.getBody()) or - not exists(fs.getInitializer(0)) and - next = getForStmtConditionOrBody(fs) + // Flow from last element of loop body back to first element of condition + not ls instanceof ForStmt and + cfe = lastLoopStmtBody(ls, c) and + c.continuesLoop() and + result = first(ls.getCondition()) ) or - // Flow from last element of initializer `i` to first element of initializer `i+1` - exists(int i | - cfe = lastForStmtInitializer(fs, i, c) | - c instanceof NormalCompletion and - result = first(fs.getInitializer(i + 1)) + cfe = any(WhileStmt ws | + // Pre-order: flow from statement itself to first element of condition + result = first(ws.getCondition()) and + c instanceof SimpleCompletion ) or - // Flow from last element of last initializer to first element of condition/loop body - exists(int last | - last = max(int i | exists(fs.getInitializer(i))) | - cfe = lastForStmtInitializer(fs, last, c) and - c instanceof NormalCompletion and - result = first(getForStmtConditionOrBody(fs)) + cfe = any(DoStmt ds | + // Pre-order: flow from statement itself to first element of body + result = first(ds.getBody()) and + c instanceof SimpleCompletion ) or - // Flow from last element of condition into first element of loop body - cfe = lastLoopStmtCondition(fs, c) and - c instanceof TrueCompletion and - result = first(fs.getBody()) - or - // Flow from last element of loop body to first element of update/condition/self - exists(ControlFlowElement next | - cfe = lastLoopStmtBody(fs, c) and - c.continuesLoop() and - result = first(next) and - if exists(fs.getUpdate(0)) then - next = fs.getUpdate(0) - else + exists(ForStmt fs | + // Pre-order: flow from statement itself to first element of first initializer/ + // condition/loop body + exists(ControlFlowElement next | + cfe = fs and + result = first(next) and + c instanceof SimpleCompletion | + next = fs.getInitializer(0) + or + not exists(fs.getInitializer(0)) and next = getForStmtConditionOrBody(fs) + ) + or + // Flow from last element of initializer `i` to first element of initializer `i+1` + exists(int i | + cfe = lastForStmtInitializer(fs, i, c) | + c instanceof NormalCompletion and + result = first(fs.getInitializer(i + 1)) + ) + or + // Flow from last element of last initializer to first element of condition/loop body + exists(int last | + last = max(int i | exists(fs.getInitializer(i))) | + cfe = lastForStmtInitializer(fs, last, c) and + c instanceof NormalCompletion and + result = first(getForStmtConditionOrBody(fs)) + ) + or + // Flow from last element of condition into first element of loop body + cfe = lastLoopStmtCondition(fs, c) and + c instanceof TrueCompletion and + result = first(fs.getBody()) + or + // Flow from last element of loop body to first element of update/condition/self + exists(ControlFlowElement next | + cfe = lastLoopStmtBody(fs, c) and + c.continuesLoop() and + result = first(next) and + if exists(fs.getUpdate(0)) then + next = fs.getUpdate(0) + else + next = getForStmtConditionOrBody(fs) + ) + or + // Flow from last element of update to first element of next update/condition/loop body + exists(ControlFlowElement next, int i | + cfe = lastForStmtUpdate(fs, i, c) and + c instanceof NormalCompletion and + result = first(next) and + if exists(fs.getUpdate(i + 1)) then + next = fs.getUpdate(i + 1) + else + next = getForStmtConditionOrBody(fs) + ) ) or - // Flow from last element of update to first element of next update/condition/loop body - exists(ControlFlowElement next, int i | - cfe = lastForStmtUpdate(fs, i, c) and + exists(ForeachStmt fs | + // Flow from last element of iterator expression to emptiness test + cfe = lastForeachStmtIterableExpr(fs, c) and c instanceof NormalCompletion and - result = first(next) and - if exists(fs.getUpdate(i + 1)) then - next = fs.getUpdate(i + 1) - else - next = getForStmtConditionOrBody(fs) - ) - ) - or - exists(ForeachStmt fs | - // Flow from last element of iterator expression to emptiness test - cfe = lastForeachStmtIterableExpr(fs, c) and - c instanceof NormalCompletion and - result = fs - or - // Flow from emptiness test to first element of variable declaration/loop body - cfe = fs and - c = any(EmptinessCompletion ec | not ec.isEmpty()) and - ( - result = first(fs.getVariableDeclExpr()) + result = fs + or + // Flow from emptiness test to first element of variable declaration/loop body + cfe = fs and + c = any(EmptinessCompletion ec | not ec.isEmpty()) and + ( + result = first(fs.getVariableDeclExpr()) + or + not exists(fs.getVariableDeclExpr()) and + result = first(fs.getBody()) + ) or - not exists(fs.getVariableDeclExpr()) and + // Flow from last element of variable declaration to first element of loop body + cfe = lastForeachStmtVariableDeclExpr(fs, c) and + c instanceof SimpleCompletion and result = first(fs.getBody()) + or + // Flow from last element of loop body back to emptiness test + cfe = lastLoopStmtBody(fs, c) and + c.continuesLoop() and + result = fs ) or - // Flow from last element of variable declaration to first element of loop body - cfe = lastForeachStmtVariableDeclExpr(fs, c) and - c instanceof SimpleCompletion and - result = first(fs.getBody()) - or - // Flow from last element of loop body back to emptiness test - cfe = lastLoopStmtBody(fs, c) and - c.continuesLoop() and - result = fs - ) - or - exists(TryStmt ts | - // Pre-order: flow from statement itself to first element of body - cfe = ts and - result = first(ts.getBlock()) and - c instanceof SimpleCompletion - or - // Flow from last element of body to first `catch` clause - exists(getAThrownException(ts, cfe, c)) and - result = first(ts.getCatchClause(0)) - or - exists(SpecificCatchClause scc, int i | - scc = ts.getCatchClause(i) | - cfe = scc and - scc = lastTryStmtCatchClause(ts, i, c) and - ( - // Flow from one `catch` clause to the next - result = first(ts.getCatchClause(i + 1)) and - c = any(MatchingCompletion mc | not mc.isMatch()) + exists(TryStmt ts | + // Pre-order: flow from statement itself to first element of body + cfe = ts and + result = first(ts.getBlock()) and + c instanceof SimpleCompletion + or + // Flow from last element of body to first `catch` clause + exists(getAThrownException(ts, cfe, c)) and + result = first(ts.getCatchClause(0)) + or + exists(SpecificCatchClause scc, int i | + scc = ts.getCatchClause(i) | + cfe = scc and + scc = lastTryStmtCatchClause(ts, i, c) and + ( + // Flow from one `catch` clause to the next + result = first(ts.getCatchClause(i + 1)) and + c = any(MatchingCompletion mc | not mc.isMatch()) + or + // Flow from last `catch` clause to first element of `finally` block + ts.getCatchClause(i).isLast() and + result = first(ts.getFinally()) and + c instanceof ThrowCompletion // inherited from `try` block + ) + or + cfe = lastTryStmtCatchClause(ts, i, c) and + cfe = lastSpecificCatchClauseFilterClause(scc, _) and + ( + // Flow from last element of `catch` clause filter to next `catch` clause + result = first(ts.getCatchClause(i + 1)) and + c instanceof FalseCompletion + or + // Flow from last element of `catch` clause filter, of last clause, to first + // element of `finally` block + ts.getCatchClause(i).isLast() and + result = first(ts.getFinally()) and + c instanceof ThrowCompletion // inherited from `try` block + ) or - // Flow from last `catch` clause to first element of `finally` block - ts.getCatchClause(i).isLast() and - result = first(ts.getFinally()) and - c instanceof ThrowCompletion // inherited from `try` block + // Flow from last element of a `catch` block to first element of `finally` block + cfe = lastCatchClauseBlock(scc, c) and + result = first(ts.getFinally()) ) or - cfe = lastTryStmtCatchClause(ts, i, c) and - cfe = lastSpecificCatchClauseFilterClause(scc, _) and + // Flow from last element of `try` block to first element of `finally` block + cfe = lastTryStmtBlock(ts, c) and + result = first(ts.getFinally()) and ( - // Flow from last element of `catch` clause filter to next `catch` clause - result = first(ts.getCatchClause(i + 1)) and - c instanceof FalseCompletion - or - // Flow from last element of `catch` clause filter, of last clause, to first - // element of `finally` block - ts.getCatchClause(i).isLast() and - result = first(ts.getFinally()) and - c instanceof ThrowCompletion // inherited from `try` block + c instanceof ThrowCompletion + implies + not exists(ts.getACatchClause()) ) - or - // Flow from last element of a `catch` block to first element of `finally` block - cfe = lastCatchClauseBlock(scc, c) and - result = first(ts.getFinally()) ) or - // Flow from last element of `try` block to first element of `finally` block - cfe = lastTryStmtBlock(ts, c) and - result = first(ts.getFinally()) and - ( - c instanceof ThrowCompletion - implies - not exists(ts.getACatchClause()) - ) - ) - or - exists(SpecificCatchClause scc | - // Flow from catch clause to variable declaration/filter clause/block - cfe = scc and - c.(MatchingCompletion).isMatch() and - exists(ControlFlowElement next | - result = first(next) | - if exists(scc.getVariableDeclExpr()) then - next = scc.getVariableDeclExpr() - else if exists(scc.getFilterClause()) then - next = scc.getFilterClause() - else - next = scc.getBlock() + exists(SpecificCatchClause scc | + // Flow from catch clause to variable declaration/filter clause/block + cfe = scc and + c.(MatchingCompletion).isMatch() and + exists(ControlFlowElement next | + result = first(next) | + if exists(scc.getVariableDeclExpr()) then + next = scc.getVariableDeclExpr() + else if exists(scc.getFilterClause()) then + next = scc.getFilterClause() + else + next = scc.getBlock() + ) + or + // Flow from variable declaration to filter clause/block + cfe = last(scc.getVariableDeclExpr(), c) and + c instanceof SimpleCompletion and + exists(ControlFlowElement next | + result = first(next) | + if exists(scc.getFilterClause()) then + next = scc.getFilterClause() + else + next = scc.getBlock() + ) + or + // Flow from filter to block + cfe = last(scc.getFilterClause(), c) and + c instanceof TrueCompletion and + result = first(scc.getBlock()) ) or - // Flow from variable declaration to filter clause/block - cfe = last(scc.getVariableDeclExpr(), c) and - c instanceof SimpleCompletion and - exists(ControlFlowElement next | - result = first(next) | - if exists(scc.getFilterClause()) then - next = scc.getFilterClause() - else - next = scc.getBlock() - ) + // Post-order: flow from last element of child to statement itself + cfe = lastJumpStmtChild(result, c) and + c instanceof NormalCompletion or - // Flow from filter to block - cfe = last(scc.getFilterClause(), c) and - c instanceof TrueCompletion and - result = first(scc.getBlock()) - ) - or - // Post-order: flow from last element of child to statement itself - cfe = lastJumpStmtChild(result, c) and - c instanceof NormalCompletion - or - // Flow from constructor initializer to first element of constructor body - cfe = any(ConstructorInitializer ci | - c instanceof SimpleCompletion and - result = first(ci.getConstructor().getBody()) - ) - or - // Flow from element with `goto` completion to first element of relevant - // target - c = any(GotoLabelCompletion glc | - cfe = last(_, glc) and - // Special case: when a `goto` happens inside a `try` statement with a - // `finally` block, flow does not go directly to the target, but instead - // to the `finally` block (and from there possibly to the target) - not cfe = getBlockOrCatchFinallyPred(any(TryStmt ts | ts.hasFinally()), _) and - result = first(glc.getGotoStmt().getTarget()) - ) - } - - /** - * Gets an exception type that is thrown by `cfe` in the block of `try` statement - * `ts`. Throw completion `c` matches the exception type. - */ - ExceptionClass getAThrownException(TryStmt ts, ControlFlowElement cfe, ThrowCompletion c) { - cfe = lastTryStmtBlock(ts, c) and - result = c.getExceptionClass() - } - - /** - * Gets the condition of `for` loop `fs` if it exists, otherwise the body. - */ - private ControlFlowElement getForStmtConditionOrBody(ForStmt fs) { - result = fs.getCondition() - or - not exists(fs.getCondition()) and - result = fs.getBody() - } - - /** - * Gets the control flow element that is first executed when entering - * callable `c`. - */ - ControlFlowElement succEntry(Callable c) { - if exists(c.(Constructor).getInitializer()) then - result = first(c.(Constructor).getInitializer()) - else - result = first(c.getBody()) - } - - /** - * Gets the callable that is exited when `cfe` finishes with completion `c`, - * if any. - */ - Callable succExit(ControlFlowElement cfe, Completion c) { - cfe = last(result.getBody(), c) and - not c instanceof GotoCompletion - } - } - import Successor - - /** - * Provides classes and predicates relevant for splitting the control flow graph. - */ - private module Splitting { - /** - * A split for a control flow element. For example, a tag that determines how to - * continue execution after leaving a `finally` block. - */ - class SplitImpl extends TSplit { - /** Gets a textual representation of this split. */ - string toString() { none() } - - /** - * INTERNAL: Do not use. - * - * Holds if this split applies to control flow element `cfe`. - */ - final predicate appliesTo(ControlFlowElement cfe) { - this.hasEntry(_, cfe, _) + // Flow from constructor initializer to first element of constructor body + cfe = any(ConstructorInitializer ci | + c instanceof SimpleCompletion and + result = first(ci.getConstructor().getBody()) + ) or - exists(ControlFlowElement pred | - this.appliesTo(pred) | - this.hasSuccessor(pred, cfe, _) + // Flow from element with `goto` completion to first element of relevant + // target + c = any(GotoLabelCompletion glc | + cfe = last(_, glc) and + // Special case: when a `goto` happens inside a `try` statement with a + // `finally` block, flow does not go directly to the target, but instead + // to the `finally` block (and from there possibly to the target) + not cfe = getBlockOrCatchFinallyPred(any(TryStmt ts | ts.hasFinally()), _) and + result = first(glc.getGotoStmt().getTarget()) ) } /** - * INTERNAL: Do not use. - * - * Holds if this split is entered when control passes from `pred` to `succ` with - * completion `c`. + * Gets an exception type that is thrown by `cfe` in the block of `try` statement + * `ts`. Throw completion `c` matches the exception type. */ - // Invariant: `hasEntry(pred, succ, c) implies succ = Successor::succ(pred, c)` - predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - none() + ExceptionClass getAThrownException(TryStmt ts, ControlFlowElement cfe, ThrowCompletion c) { + cfe = lastTryStmtBlock(ts, c) and + result = c.getExceptionClass() } /** - * INTERNAL: Do not use. - * - * Holds if this split is left when control passes from `pred` to `succ` with - * completion `c`. + * Gets the condition of `for` loop `fs` if it exists, otherwise the body. */ - // Invariant: `hasExit(pred, succ, c) implies succ = Successor::succ(pred, c)` - predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - none() + private ControlFlowElement getForStmtConditionOrBody(ForStmt fs) { + result = fs.getCondition() + or + not exists(fs.getCondition()) and + result = fs.getBody() } /** - * INTERNAL: Do not use. - * - * Holds if this split is left when control passes from `pred` out of the enclosing - * callable with completion `c`. + * Gets the control flow element that is first executed when entering + * callable `c`. */ - // Invariant: `hasExit(pred, c) implies pred.getEnclosingCallable() = Successor::succExit(pred, c)` - predicate hasExit(ControlFlowElement pred, Completion c) { - none() + ControlFlowElement succEntry(Callable c) { + if exists(c.(Constructor).getInitializer()) then + result = first(c.(Constructor).getInitializer()) + else + result = first(c.getBody()) } /** - * INTERNAL: Do not use. - * - * Holds if this split is maintained when control passes from `pred` to `succ` with - * completion `c`. + * Gets the callable that is exited when `cfe` finishes with completion `c`, + * if any. */ - // Invariant: `hasSuccessor(pred, succ, c) implies succ = Successor::succ(pred, c)` - predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - none() + Callable succExit(ControlFlowElement cfe, Completion c) { + cfe = last(result.getBody(), c) and + not c instanceof GotoCompletion } } + import Successor - module FinallySplitting { + /** + * Provides classes and predicates relevant for splitting the control flow graph. + */ + private module Splitting { /** - * The type of a split `finally` node. - * - * The type represents one of the possible ways of entering a `finally` - * block. For example, if a `try` statement ends with a `return` statement, - * then the `finally` block must end with a `return` as well (provided that - * the `finally` block exits normally). + * A split for a control flow element. For example, a tag that determines how to + * continue execution after leaving a `finally` block. */ - class FinallySplitType extends ControlFlowEdgeType { - FinallySplitType() { - not this instanceof ControlFlowEdgeConditional + class SplitImpl extends TSplit { + /** Gets a textual representation of this split. */ + string toString() { none() } + + /** + * INTERNAL: Do not use. + * + * Holds if this split applies to control flow element `cfe`. + */ + final predicate appliesTo(ControlFlowElement cfe) { + this.hasEntry(_, cfe, _) + or + exists(ControlFlowElement pred | + this.appliesTo(pred) | + this.hasSuccessor(pred, cfe, _) + ) } - /** Holds if this split type matches entry into a `finally` block with completion `c`. */ - predicate isSplitForEntryCompletion(Completion c) { - if c instanceof NormalCompletion then - // If the entry into the `finally` block completes with any normal completion, - // it simply means normal execution after the `finally` block - this instanceof ControlFlowEdgeSuccessor - else - this.matchesCompletion(c) + /** + * INTERNAL: Do not use. + * + * Holds if this split is entered when control passes from `pred` to `succ` with + * completion `c`. + */ + // Invariant: `hasEntry(pred, succ, c) implies succ = Successor::succ(pred, c)` + predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + none() } - } - /** - * Gets a descendant that belongs to the `finally` block of try statement - * `try`. - */ - ControlFlowElement getAFinallyDescendant(TryStmt try) { - result = try.getFinally() - or - exists(ControlFlowElement mid | - mid = getAFinallyDescendant(try) and - result = mid.getAChild() and - mid.getEnclosingCallable() = result.getEnclosingCallable() and - not exists(TryStmt nestedTry | - result = nestedTry.getFinally() and - nestedTry != try - ) - ) + /** + * INTERNAL: Do not use. + * + * Holds if this split is left when control passes from `pred` to `succ` with + * completion `c`. + */ + // Invariant: `hasExit(pred, succ, c) implies succ = Successor::succ(pred, c)` + predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + none() + } + + /** + * INTERNAL: Do not use. + * + * Holds if this split is left when control passes from `pred` out of the enclosing + * callable with completion `c`. + */ + // Invariant: `hasExit(pred, c) implies pred.getEnclosingCallable() = Successor::succExit(pred, c)` + predicate hasExit(ControlFlowElement pred, Completion c) { + none() + } + + /** + * INTERNAL: Do not use. + * + * Holds if this split is maintained when control passes from `pred` to `succ` with + * completion `c`. + */ + // Invariant: `hasSuccessor(pred, succ, c) implies succ = Successor::succ(pred, c)` + predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + none() + } } - /** A control flow element that belongs to a `finally` block. */ - private class FinallyControlFlowElement extends ControlFlowElement { - FinallyControlFlowElement() { - this = getAFinallyDescendant(_) + module FinallySplitting { + /** + * The type of a split `finally` node. + * + * The type represents one of the possible ways of entering a `finally` + * block. For example, if a `try` statement ends with a `return` statement, + * then the `finally` block must end with a `return` as well (provided that + * the `finally` block exits normally). + */ + class FinallySplitType extends SuccessorType { + FinallySplitType() { + not this instanceof SuccessorTypes::ConditionalSuccessor + } + + /** Holds if this split type matches entry into a `finally` block with completion `c`. */ + predicate isSplitForEntryCompletion(Completion c) { + if c instanceof NormalCompletion then + // If the entry into the `finally` block completes with any normal completion, + // it simply means normal execution after the `finally` block + this instanceof SuccessorTypes::NormalSuccessor + else + this.matchesCompletion(c) + } } - /** Holds if this node is the entry node in the `finally` block it belongs to. */ - predicate isEntryNode() { - exists(TryStmt try | - this = getAFinallyDescendant(try) | - this = first(try.getFinally()) + /** + * Gets a descendant that belongs to the `finally` block of try statement + * `try`. + */ + ControlFlowElement getAFinallyDescendant(TryStmt try) { + result = try.getFinally() + or + exists(ControlFlowElement mid | + mid = getAFinallyDescendant(try) and + result = mid.getAChild() and + mid.getEnclosingCallable() = result.getEnclosingCallable() and + not exists(TryStmt nestedTry | + result = nestedTry.getFinally() and + nestedTry != try + ) ) } + /** A control flow element that belongs to a `finally` block. */ + private class FinallyControlFlowElement extends ControlFlowElement { + FinallyControlFlowElement() { + this = getAFinallyDescendant(_) + } + + /** Holds if this node is the entry node in the `finally` block it belongs to. */ + predicate isEntryNode() { + exists(TryStmt try | + this = getAFinallyDescendant(try) | + this = first(try.getFinally()) + ) + } + + /** + * Holds if this node is a last element in the `finally` block belonging to + * `try` statement `try`, with completion `c`. + */ + predicate isExitNode(TryStmt try, Completion c) { + this = getAFinallyDescendant(try) and + this = lastTryStmtFinally(try, c) + } + } + + /** A control flow element that does not belong to a `finally` block. */ + private class NonFinallyControlFlowElement extends ControlFlowElement { + NonFinallyControlFlowElement() { + not this = getAFinallyDescendant(_) + } + } + /** - * Holds if this node is a last element in the `finally` block belonging to - * `try` statement `try`, with completion `c`. + * A split for elements belonging to a `finally` block, which determines how to + * continue execution after leaving the `finally` block. For example, in + * + * ``` + * try + * { + * if (!M()) + * throw new Exception(); + * } + * finally + * { + * Log.Write("M failed"); + * } + * ``` + * + * all control flow nodes in the `finally` block have two splits: one representing + * normal execution of the `try` block (when `M()` returns `true`), and one + * representing exceptional execution of the `try` block (when `M()` returns `false`). */ - predicate isExitNode(TryStmt try, Completion c) { - this = getAFinallyDescendant(try) and - this = lastTryStmtFinally(try, c) + class FinallySplitImpl extends Nodes::Split, TFinallySplit { + private FinallySplitType type; + + FinallySplitImpl() { this = TFinallySplit(type) } + + /** + * Gets the type of this `finally` split, that is, how to continue execution after the + * `finally` block. + */ + FinallySplitType getType() { + result = type + } + + override string toString() { + if type instanceof SuccessorTypes::NormalSuccessor then + result = "" + else + result = "finally: " + type.toString() + } + + override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + succ.(FinallyControlFlowElement).isEntryNode() and + succ = succ(pred, c) and + type.isSplitForEntryCompletion(c) and + ( + // Abnormal entry must enter the correct splitting + not c instanceof NormalCompletion + or + // Normal entry only when not entering a nested `finally` block; in that case, + // the outer split must be maintained (see `hasSuccessor()`) + pred instanceof NonFinallyControlFlowElement + ) + } + + /** + * Holds if this split applies to control flow element `pred`, where `pred` + * is a valid predecessor. + */ + private predicate appliesToPredecessor(ControlFlowElement pred) { + this.appliesTo(pred) and + (exists(succ(pred, _)) or exists(succExit(pred, _))) + } + + /** Holds if `pred` may exit this split with completion `c`. */ + private predicate exit(ControlFlowElement pred, Completion c) { + this.appliesToPredecessor(pred) and + exists(TryStmt try | + pred = last(try, c) | + if pred.(FinallyControlFlowElement).isExitNode(try, c) then ( + // Finally block can itself exit with completion `c`: either `c` must + // match this split, `c` must be an abnormal completion, or this split + // does not require another completion to be recovered + type.matchesCompletion(c) + or + not c instanceof NormalCompletion + or + type instanceof SuccessorTypes::NormalSuccessor + ) else ( + // Finally block can exit with completion `c` derived from try/catch + // block: must match this split + type.matchesCompletion(c) and + not type instanceof SuccessorTypes::NormalSuccessor + ) + ) + } + + override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + this.appliesToPredecessor(pred) and + succ = succ(pred, c) and + ( + // Entering a nested `finally` block abnormally means that we should exit this split + succ.(FinallyControlFlowElement).isEntryNode() and + not c instanceof NormalCompletion + or + exit(pred, c) + or + exit(pred, any(BreakCompletion bc)) and + c instanceof BreakNormalCompletion + ) + } + + override predicate hasExit(ControlFlowElement pred, Completion c) { + exists(succExit(pred, c)) and + ( + exit(pred, c) + or + exit(pred, any(BreakCompletion bc)) and + c instanceof BreakNormalCompletion + ) + } + + override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + this.appliesToPredecessor(pred) and + succ = succ(pred, c) and + succ = any(FinallyControlFlowElement fcfe | + if fcfe.isEntryNode() then + // Entering a nested `finally` block normally must remember the outer split + c instanceof NormalCompletion + else + // Staying in the same `finally` block should maintain this split + not this.hasEntry(pred, succ, c) + ) + } } } - /** A control flow element that does not belong to a `finally` block. */ - private class NonFinallyControlFlowElement extends ControlFlowElement { - NonFinallyControlFlowElement() { - not this = getAFinallyDescendant(_) + module ExceptionHandlerSplitting { + private newtype TMatch = TAlways() or TMaybe() or TNever() + + /** + * A split for elements belonging to a `catch` clause, which determines the type of + * exception to handle. For example, in + * + * ``` + * try + * { + * if (M() > 0) + * throw new ArgumentException(); + * else if (M() < 0) + * throw new ArithmeticException("negative"); + * else + * return; + * } + * catch (ArgumentException e) + * { + * Log.Write("M() positive"); + * } + * catch (ArithmeticException e) when (e.Message != null) + * { + * Log.Write($"M() {e.Message}"); + * } + * ``` + * + * all control flow nodes in + * ``` + * catch (ArgumentException e) + * ``` + * and + * ``` + * catch (ArithmeticException e) when (e.Message != null) + * ``` + * have two splits: one representing the `try` block throwing an `ArgumentException`, + * and one representing the `try` block throwing an `ArithmeticException`. + */ + class ExceptionHandlerSplitImpl extends Nodes::Split, TExceptionHandlerSplit { + private ExceptionClass ec; + + ExceptionHandlerSplitImpl() { + this = TExceptionHandlerSplit(ec) + } + + override string toString() { + result = "exception: " + ec.toString() + } + + override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + // Entry into first catch clause + exists(TryStmt ts | + ec = getAThrownException(ts, pred, c) | + succ = succ(pred, c) and + succ = ts.getCatchClause(0).(SpecificCatchClause) + ) + } + + /** + * Holds if this split applies to catch clause `scc`. The parameter `match` + * indicates whether the catch clause `scc` may match the exception type of + * this split. + */ + private predicate appliesToCatchClause(SpecificCatchClause scc, TMatch match) { + exists(TryStmt ts | + ec = getAThrownException(ts, _, _) and + scc = ts.getACatchClause() | + if scc.getCaughtExceptionType() = ec.getABaseType*() then + match = TAlways() + else if scc.getCaughtExceptionType() = ec.getASubType+() then + match = TMaybe() + else + match = TNever() + ) + } + + /** + * Holds if this split applies to control flow element `pred`, where `pred` + * is a valid predecessor with completion `c`. + */ + private predicate appliesToPredecessor(ControlFlowElement pred, Completion c) { + this.appliesTo(pred) and + (exists(succ(pred, c)) or exists(succExit(pred, c))) and + ( + pred instanceof SpecificCatchClause + implies + pred = any(SpecificCatchClause scc | + if c instanceof MatchingCompletion then + exists(TMatch match | + this.appliesToCatchClause(scc, match) | + c = any(MatchingCompletion mc | + if mc.isMatch() then + match != TNever() + else + match != TAlways() + ) + ) + else ( + (scc.isLast() and c instanceof ThrowCompletion) + implies + exists(TMatch match | + this.appliesToCatchClause(scc, match) | + match != TAlways() + ) + ) + ) + ) + } + + /** + * Holds if this split applies to `pred`, and `pred` may exit this split + * with throw completion `c`, because it belongs to the last `catch` clause + * in a `try` statement. + */ + private predicate hasLastExit(ControlFlowElement pred, ThrowCompletion c) { + this.appliesToPredecessor(pred, c) and + exists(TryStmt ts, SpecificCatchClause scc, int last | + pred = lastTryStmtCatchClause(ts, last, c) | + ts.getCatchClause(last) = scc and + scc.isLast() and + c.getExceptionClass() = ec + ) + } + + override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + this.appliesToPredecessor(pred, c) and + succ = succ(pred, c) and + ( + // Exit out to `catch` clause block + succ = first(any(SpecificCatchClause scc).getBlock()) + or + // Exit out to a general `catch` clause + succ instanceof GeneralCatchClause + or + // Exit out from last `catch` clause (no catch clauses match) + this.hasLastExit(pred, c) + ) + } + + override predicate hasExit(ControlFlowElement pred, Completion c) { + // Exit out from last `catch` clause (no catch clauses match) + this.hasLastExit(pred, c) and + exists(succExit(pred, c)) + } + + override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + this.appliesToPredecessor(pred, c) and + succ = succ(pred, c) and + not succ = first(any(SpecificCatchClause scc).getBlock()) and + not succ instanceof GeneralCatchClause and + not exists(TryStmt ts, SpecificCatchClause scc, int last | + pred = lastTryStmtCatchClause(ts, last, c) | + ts.getCatchClause(last) = scc and + scc.isLast() + ) + } } } /** - * A split for elements belonging to a `finally` block, which determines how to - * continue execution after leaving the `finally` block. For example, in - * - * ``` - * try - * { - * if (!M()) - * throw new Exception(); - * } - * finally - * { - * Log.Write("M failed"); - * } - * ``` - * - * all control flow nodes in the `finally` block have two splits: one representing - * normal execution of the `try` block (when `M()` returns `true`), and one - * representing exceptional execution of the `try` block (when `M()` returns `false`). + * Gets an integer representing the kind of split `s`. The kind is used + * to make an arbitrary order on splits. */ - class FinallySplitImpl extends Split, TFinallySplit { - private FinallySplitType type; + private int getSplitKind(Nodes::Split s) { + s = TFinallySplit(_) and result = 0 + or + s = TExceptionHandlerSplit(_) and result = 1 + } - FinallySplitImpl() { this = TFinallySplit(type) } + /** Gets the rank of `split` among all the splits that apply to `cfe`. */ + int getSplitRank(Nodes::Split split, ControlFlowElement cfe) { + split.appliesTo(cfe) and + getSplitKind(split) = rank[result](int i | + i = getSplitKind(any(Nodes::Split s | s.appliesTo(cfe))) + ) + } + /** + * A set of control flow node splits. The set is represented by a list of splits, + * ordered by rank. + */ + class Splits extends TSplits { /** - * Gets the type of this `finally` split, that is, how to continue execution after the - * `finally` block. + * Holds if this subset of splits, `s_n :: ... s_1 :: NIL`, applies to control + * flow element `cfe`. That is, `i = getSplitRank(s_i, cfe)`, for `i = 1 .. n`, + * and `rnk = getSplitRank(s_n)`. */ - FinallySplitType getType() { - result = type - } - - override string toString() { - if type instanceof ControlFlowEdgeSuccessor then - result = "" - else - result = "finally: " + type.toString() - } - - override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - succ.(FinallyControlFlowElement).isEntryNode() and - succ = succ(pred, c) and - type.isSplitForEntryCompletion(c) and - ( - // Abnormal entry must enter the correct splitting - not c instanceof NormalCompletion - or - // Normal entry only when not entering a nested `finally` block; in that case, - // the outer split must be maintained (see `hasSuccessor()`) - pred instanceof NonFinallyControlFlowElement + private predicate appliesToSub(ControlFlowElement cfe, int rnk) { + exists(Nodes::Split last | + this = TSplitsCons(last, TSplitsNil()) | + rnk = getSplitRank(last, cfe) and + rnk = 1 + ) + or + exists(Nodes::Split head, Splits tail | + this = TSplitsCons(head, tail) | + tail.appliesToSub(cfe, rnk - 1) and + rnk = getSplitRank(head, cfe) ) } - - /** - * Holds if this split applies to control flow element `pred`, where `pred` - * is a valid predecessor. - */ - private predicate appliesToPredecessor(ControlFlowElement pred) { - this.appliesTo(pred) and - (exists(succ(pred, _)) or exists(succExit(pred, _))) + + /** Holds if this set of splits applies to control flow element `cfe`. */ + predicate appliesTo(ControlFlowElement cfe) { + this = TSplitsNil() and + not exists(Nodes::Split s | s.appliesTo(cfe)) + or + this.appliesToSub(cfe, max(getSplitRank(_, cfe))) } - /** Holds if `pred` may exit this split with completion `c`. */ - private predicate exit(ControlFlowElement pred, Completion c) { - this.appliesToPredecessor(pred) and - exists(TryStmt try | - pred = last(try, c) | - if pred.(FinallyControlFlowElement).isExitNode(try, c) then ( - // Finally block can itself exit with completion `c`: either `c` must - // match this split, `c` must be an abnormal completion, or this split - // does not require another completion to be recovered - type.matchesCompletion(c) - or - not c instanceof NormalCompletion - or - type instanceof ControlFlowEdgeSuccessor - ) else ( - // Finally block can exit with completion `c` derived from try/catch - // block: must match this split - type.matchesCompletion(c) and - not type instanceof ControlFlowEdgeSuccessor - ) + /** Gets a textual representation of this set of splits. */ + string toString() { + this = TSplitsNil() and + result = "" + or + exists(Nodes::Split head, Splits tail, string res | + this = TSplitsCons(head, tail) | + res = tail.toString() and + if res = "" then + result = head.toString() + else + result = head.toString() + ", " + res ) } - override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - this.appliesToPredecessor(pred) and - succ = succ(pred, c) and - ( - // Entering a nested `finally` block abnormally means that we should exit this split - succ.(FinallyControlFlowElement).isEntryNode() and - not c instanceof NormalCompletion + /** Gets a split belonging to this set of splits. */ + Nodes::Split getASplit() { + exists(Nodes::Split head, Splits tail | + this = TSplitsCons(head, tail) | + result = head or - exit(pred, c) - or - exit(pred, any(BreakCompletion bc)) and - c instanceof BreakNormalCompletion + result = tail.getASplit() ) } + } - override predicate hasExit(ControlFlowElement pred, Completion c) { - exists(succExit(pred, c)) and - ( - exit(pred, c) + /** + * Holds if `succ` with splits `succSplits` is the first element that is executed + * when entering callable `pred`. + */ + pragma [noinline] + predicate succEntrySplits(Callable pred, ControlFlowElement succ, Splits succSplits, SuccessorType t) { + succ = succEntry(pred) and + t instanceof SuccessorTypes::NormalSuccessor and + succSplits = TSplitsNil() // initially no splits + } + + pragma [noinline] + private predicate succSplits0(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, Completion c) { + succ = succ(pred, c) and + predSplits.appliesTo(pred) and + succSplits.appliesTo(succ) + } + + pragma [noinline] + private predicate succSplits1(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, Completion c) { + succSplits0(pred, predSplits, succ, succSplits, c) and + // Each successor split must be either newly entered into, or must be + // passed over from a predecessor split + forall(Nodes::Split succSplit | + succSplit = succSplits.getASplit() | + succSplit.hasEntry(pred, succ, c) + or + exists(Nodes::Split predSplit | + predSplit = predSplits.getASplit() | + succSplit = predSplit and + predSplit.hasSuccessor(pred, succ, c) + ) + ) + } + + /** + * Holds if `succ` with splits `succSplits` is a successor of type `t` for `pred` + * with splits `predSplits`. + */ + predicate succSplits(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, SuccessorType t) { + exists(Completion c | + t.matchesCompletion(c) | + succSplits1(pred, predSplits, succ, succSplits, c) and + // Enter a new split when required + forall(Nodes::Split split | + split.hasEntry(pred, succ, c) | + succSplits.getASplit() = split + ) and + // Each predecessor split must be either passed over as a successor split, + // or must be left (possibly entering a new split) + forall(Nodes::Split predSplit | + predSplit = predSplits.getASplit() | + predSplit.hasSuccessor(pred, succ, c) and + predSplit = succSplits.getASplit() or - exit(pred, any(BreakCompletion bc)) and - c instanceof BreakNormalCompletion + predSplit.hasExit(pred, succ, c) and + forall(Nodes::Split succSplit | + succSplit = succSplits.getASplit() | + getSplitKind(succSplit) != getSplitKind(predSplit) + or + succSplit.hasEntry(pred, succ, c) + ) ) - } + ) + } - override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - this.appliesToPredecessor(pred) and - succ = succ(pred, c) and - succ = any(FinallyControlFlowElement fcfe | - if fcfe.isEntryNode() then - // Entering a nested `finally` block normally must remember the outer split - c instanceof NormalCompletion - else - // Staying in the same `finally` block should maintain this split - not this.hasEntry(pred, succ, c) + /** + * Holds if `pred` with splits `predSplits` can exit the enclosing callable + * `succ` with type `t`. + */ + predicate succExitSplits(ControlFlowElement pred, Splits predSplits, Callable succ, SuccessorType t) { + exists(Completion c | + t.matchesCompletion(c) | + succ = succExit(pred, c) and + predSplits.appliesTo(pred) and + forall(Nodes::Split predSplit | + predSplit = predSplits.getASplit() | + predSplit.hasExit(pred, c) ) - } + ) } } + import Splitting - module ExceptionHandlerSplitting { - private newtype TMatch = TAlways() or TMaybe() or TNever() - + /** Provides logic for calculating reachable control flow nodes. */ + module Reachability { /** - * A split for elements belonging to a `catch` clause, which determines the type of - * exception to handle. For example, in - * - * ``` - * try - * { - * if (M() > 0) - * throw new ArgumentException(); - * else if (M() < 0) - * throw new ArithmeticException("negative"); - * else - * return; - * } - * catch (ArgumentException e) - * { - * Log.Write("M() positive"); - * } - * catch (ArithmeticException e) when (e.Message != null) - * { - * Log.Write($"M() {e.Message}"); - * } - * ``` - * - * all control flow nodes in - * ``` - * catch (ArgumentException e) - * ``` - * and - * ``` - * catch (ArithmeticException e) when (e.Message != null) - * ``` - * have two splits: one representing the `try` block throwing an `ArgumentException`, - * and one representing the `try` block throwing an `ArithmeticException`. + * Holds if `cfe` is a control flow element where the set of possible splits may + * be different from the set of possible splits for one of `cfe`'s predecessors. + * That is, `cfe` starts a new block of elements with the same set of splits. */ - class ExceptionHandlerSplitImpl extends Split, TExceptionHandlerSplit { - private ExceptionClass ec; + private predicate startsSplits(ControlFlowElement cfe) { + cfe = succEntry(_) + or + exists(Nodes::Split s | + s.hasEntry(_, cfe, _) + or + s.hasExit(_, cfe, _) + ) + } + + private predicate intraSplitsSucc(ControlFlowElement pred, ControlFlowElement succ) { + succ = succ(pred, _) and + not startsSplits(succ) + } - ExceptionHandlerSplitImpl() { - this = TExceptionHandlerSplit(ec) + private predicate splitsBlockContains(ControlFlowElement start, ControlFlowElement cfe) = + fastTC(intraSplitsSucc/2)(start, cfe) + + /** + * A block of control flow elements where the set of splits is guaranteed + * to remain unchanged, represented by the first element in the block. + */ + class SameSplitsBlock extends ControlFlowElement { + SameSplitsBlock() { + startsSplits(this) } - override string toString() { - result = "exception: " + ec.toString() + /** Gets a control flow element in this block. */ + ControlFlowElement getAnElement() { + splitsBlockContains(this, result) + or + result = this } - override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - // Entry into first catch clause - exists(TryStmt ts | - ec = getAThrownException(ts, pred, c) | - succ = succ(pred, c) and - succ = ts.getCatchClause(0).(SpecificCatchClause) + /** Gets a successor block, where the splits may be different. */ + SameSplitsBlock getASuccessor(Splits predSplits, Splits succSplits) { + exists(ControlFlowElement pred | + pred = this.getAnElement() | + succSplits(pred, predSplits, result, succSplits, _) ) } /** - * Holds if this split applies to catch clause `scc`. The parameter `match` - * indicates whether the catch clause `scc` may match the exception type of - * this split. + * Holds if the elements of this block are reachable from a callable entry + * point, with the splits `splits`. */ - private predicate appliesToCatchClause(SpecificCatchClause scc, TMatch match) { - exists(TryStmt ts | - ec = getAThrownException(ts, _, _) and - scc = ts.getACatchClause() | - if scc.getCaughtExceptionType() = ec.getABaseType*() then - match = TAlways() - else if scc.getCaughtExceptionType() = ec.getASubType+() then - match = TMaybe() - else - match = TNever() + predicate isReachable(Splits splits) { + // Base case + succEntrySplits(_, this, splits, _) + or + // Recursive case + exists(SameSplitsBlock pred, Splits predSplits | + pred.isReachable(predSplits) | + this = pred.getASuccessor(predSplits, splits) ) } + } + } - /** - * Holds if this split applies to control flow element `pred`, where `pred` - * is a valid predecessor with completion `c`. - */ - private predicate appliesToPredecessor(ControlFlowElement pred, Completion c) { - this.appliesTo(pred) and - (exists(succ(pred, c)) or exists(succExit(pred, c))) and - ( - pred instanceof SpecificCatchClause - implies - pred = any(SpecificCatchClause scc | - if c instanceof MatchingCompletion then - exists(TMatch match | - this.appliesToCatchClause(scc, match) | - c = any(MatchingCompletion mc | - if mc.isMatch() then - match != TNever() - else - match != TAlways() - ) - ) - else ( - (scc.isLast() and c instanceof ThrowCompletion) - implies - exists(TMatch match | - this.appliesToCatchClause(scc, match) | - match != TAlways() - ) - ) + private cached module Cached { + cached + newtype TSplit = + TFinallySplit(FinallySplitting::FinallySplitType type) + or + TExceptionHandlerSplit(ExceptionClass ec) + + cached + newtype TSplits = + TSplitsNil() + or + TSplitsCons(Nodes::Split head, Splits tail) { + exists(int rnk, ControlFlowElement cfe | + rnk = getSplitRank(head, cfe) | + rnk = 1 and + tail = TSplitsNil() + or + exists(Nodes::Split tailHead | + tail = TSplitsCons(tailHead, _) | + rnk - 1 = getSplitRank(tailHead, cfe) ) ) } - /** - * Holds if this split applies to `pred`, and `pred` may exit this split - * with throw completion `c`, because it belongs to the last `catch` clause - * in a `try` statement. - */ - private predicate hasLastExit(ControlFlowElement pred, ThrowCompletion c) { - this.appliesToPredecessor(pred, c) and - exists(TryStmt ts, SpecificCatchClause scc, int last | - pred = lastTryStmtCatchClause(ts, last, c) | - ts.getCatchClause(last) = scc and - scc.isLast() and - c.getExceptionClass() = ec + /** + * Internal representation of control flow nodes in the control flow graph. + * The control flow graph is pruned for unreachable nodes. + */ + cached + newtype TNode = + TEntryNode(Callable c) { + succEntrySplits(c, _, _, _) + } + or + TExitNode(Callable c) { + exists(Reachability::SameSplitsBlock b | + b.isReachable(_) | + succExitSplits(b.getAnElement(), _, c, _) ) } - - override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - this.appliesToPredecessor(pred, c) and - succ = succ(pred, c) and - ( - // Exit out to `catch` clause block - succ = first(any(SpecificCatchClause scc).getBlock()) - or - // Exit out to a general `catch` clause - succ instanceof GeneralCatchClause - or - // Exit out from last `catch` clause (no catch clauses match) - this.hasLastExit(pred, c) + or + TElementNode(ControlFlowElement cfe, Splits splits) { + exists(Reachability::SameSplitsBlock b | + b.isReachable(splits) | + cfe = b.getAnElement() ) } - override predicate hasExit(ControlFlowElement pred, Completion c) { - // Exit out from last `catch` clause (no catch clauses match) - this.hasLastExit(pred, c) and - exists(succExit(pred, c)) + /** Internal representation of types of control flow. */ + cached + newtype TSuccessorType = + TSuccessorSuccessor() + or + TBooleanSuccessor(boolean b) { + b = true or b = false } - - override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - this.appliesToPredecessor(pred, c) and - succ = succ(pred, c) and - not succ = first(any(SpecificCatchClause scc).getBlock()) and - not succ instanceof GeneralCatchClause and - not exists(TryStmt ts, SpecificCatchClause scc, int last | - pred = lastTryStmtCatchClause(ts, last, c) | - ts.getCatchClause(last) = scc and - scc.isLast() - ) + or + TNullnessSuccessor(boolean isNull) { + isNull = true or isNull = false + } + or + TMatchingSuccessor(boolean isMatch) { + isMatch = true or isMatch = false + } + or + TEmptinessSuccessor(boolean isEmpty) { + isEmpty = true or isEmpty = false + } + or + TReturnSuccessor() + or + TBreakSuccessor() + or + TContinueSuccessor() + or + TGotoLabelSuccessor(GotoLabelStmt goto) + or + TGotoCaseSuccessor(GotoCaseStmt goto) + or + TGotoDefaultSuccessor() + or + TExceptionSuccessor(ExceptionClass ec) { + exists(ThrowCompletion c | c.getExceptionClass() = ec) } - } - } - - /** - * Gets an integer representing the kind of split `s`. The kind is used - * to make an arbitrary order on splits. - */ - private int getSplitKind(Split s) { - s = TFinallySplit(_) and result = 0 - or - s = TExceptionHandlerSplit(_) and result = 1 - } - - /** Gets the rank of `split` among all the splits that apply to `cfe`. */ - int getSplitRank(Split split, ControlFlowElement cfe) { - split.appliesTo(cfe) and - getSplitKind(split) = rank[result](int i | - i = getSplitKind(any(Split s | s.appliesTo(cfe))) - ) - } - /** - * A set of control flow node splits. The set is represented by a list of splits, - * ordered by rank. - */ - class Splits extends TSplits { - /** - * Holds if this subset of splits, `s_n :: ... s_1 :: NIL`, applies to control - * flow element `cfe`. That is, `i = getSplitRank(s_i, cfe)`, for `i = 1 .. n`, - * and `rnk = getSplitRank(s_n)`. - */ - private predicate appliesToSub(ControlFlowElement cfe, int rnk) { - exists(Split last | - this = TSplitsCons(last, TSplitsNil()) | - rnk = getSplitRank(last, cfe) and - rnk = 1 + /** Gets a successor node of a given flow type, if any. */ + cached + Node getASuccessorByType(Node pred, SuccessorType t) { + // Callable entry node -> callable body + exists(ControlFlowElement succElement, Splits succSplits | + result = TElementNode(succElement, succSplits) | + succEntrySplits(pred.(Nodes::EntryNode).getCallable(), succElement, succSplits, t) ) or - exists(Split head, Splits tail | - this = TSplitsCons(head, tail) | - tail.appliesToSub(cfe, rnk - 1) and - rnk = getSplitRank(head, cfe) + exists(ControlFlowElement predElement, Splits predSplits | + pred = TElementNode(predElement, predSplits) | + // Element node -> callable exit + succExitSplits(predElement, predSplits, result.(Nodes::ExitNode).getCallable(), t) + or + // Element node -> element node + exists(ControlFlowElement succElement, Splits succSplits | + result = TElementNode(succElement, succSplits) | + succSplits(predElement, predSplits, succElement, succSplits, t) + ) ) } - /** Holds if this set of splits applies to control flow element `cfe`. */ - predicate appliesTo(ControlFlowElement cfe) { - this = TSplitsNil() and - not exists(Split s | s.appliesTo(cfe)) - or - this.appliesToSub(cfe, max(getSplitRank(_, cfe))) - } - - /** Gets a textual representation of this set of splits. */ - string toString() { - this = TSplitsNil() and - result = "" - or - exists(Split head, Splits tail, string res | - this = TSplitsCons(head, tail) | - res = tail.toString() and - if res = "" then - result = head.toString() - else - result = head.toString() + ", " + res - ) + /** + * Gets a first control flow element executed within `cfe`. + */ + cached + ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { + result = first(cfe) } - /** Gets a split belonging to this set of splits. */ - Split getASplit() { - exists(Split head, Splits tail | - this = TSplitsCons(head, tail) | - result = head - or - result = tail.getASplit() - ) + /** + * Gets a potential last control flow element executed within `cfe`. + */ + cached + ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { + result = last(cfe, _) } } + import Cached + } + private import Internal +} - /** - * Holds if `succ` with splits `succSplits` is the first element that is executed - * when entering callable `pred`. - */ - pragma [noinline] - predicate succEntrySplits(Callable pred, ControlFlowElement succ, Splits succSplits, ControlFlowEdgeType t) { - succ = succEntry(pred) and - t instanceof ControlFlowEdgeSuccessor and - succSplits = TSplitsNil() // initially no splits - } +// The code below is all for backwards-compatibility; should be deleted eventually - pragma [noinline] - private predicate succSplits0(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, Completion c) { - succ = succ(pred, c) and - predSplits.appliesTo(pred) and - succSplits.appliesTo(succ) - } +deprecated +class ControlFlowNode = ControlFlow::Node; - pragma [noinline] - private predicate succSplits1(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, Completion c) { - succSplits0(pred, predSplits, succ, succSplits, c) and - // Each successor split must be either newly entered into, or must be - // passed over from a predecessor split - forall(Split succSplit | - succSplit = succSplits.getASplit() | - succSplit.hasEntry(pred, succ, c) - or - exists(Split predSplit | - predSplit = predSplits.getASplit() | - succSplit = predSplit and - predSplit.hasSuccessor(pred, succ, c) - ) - ) - } +deprecated +class CallableEntryNode = ControlFlow::Nodes::EntryNode; - /** - * Holds if `succ` with splits `succSplits` is a successor of type `t` for `pred` - * with splits `predSplits`. - */ - predicate succSplits(ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Splits succSplits, ControlFlowEdgeType t) { - exists(Completion c | - t.matchesCompletion(c) | - succSplits1(pred, predSplits, succ, succSplits, c) and - // Enter a new split when required - forall(Split split | - split.hasEntry(pred, succ, c) | - succSplits.getASplit() = split - ) and - // Each predecessor split must be either passed over as a successor split, - // or must be left (possibly entering a new split) - forall(Split predSplit | - predSplit = predSplits.getASplit() | - predSplit.hasSuccessor(pred, succ, c) and - predSplit = succSplits.getASplit() - or - predSplit.hasExit(pred, succ, c) and - forall(Split succSplit | - succSplit = succSplits.getASplit() | - getSplitKind(succSplit) != getSplitKind(predSplit) - or - succSplit.hasEntry(pred, succ, c) - ) - ) - ) - } +deprecated +class CallableExitNode = ControlFlow::Nodes::ExitNode; - /** - * Holds if `pred` with splits `predSplits` can exit the enclosing callable - * `succ` with type `t`. - */ - predicate succExitSplits(ControlFlowElement pred, Splits predSplits, Callable succ, ControlFlowEdgeType t) { - exists(Completion c | - t.matchesCompletion(c) | - succ = succExit(pred, c) and - predSplits.appliesTo(pred) and - forall(Split predSplit | - predSplit = predSplits.getASplit() | - predSplit.hasExit(pred, c) - ) - ) - } +/** + * DEPRECATED: Use `ElementNode` instead. + * + * A node for a control flow element. + */ +deprecated +class NormalControlFlowNode extends ControlFlow::Nodes::ElementNode { + NormalControlFlowNode() { + forall(ControlFlow::Nodes::FinallySplit s | + s = this.getASplit() | + s.getType() instanceof ControlFlow::SuccessorTypes::NormalSuccessor + ) } - import Splitting +} - /** Provides logic for calculating reachable control flow nodes. */ - module Reachability { - /** - * Holds if `cfe` is a control flow element where the set of possible splits may - * be different from the set of possible splits for one of `cfe`'s predecessors. - * That is, `cfe` starts a new block of elements with the same set of splits. - */ - private predicate startsSplits(ControlFlowElement cfe) { - cfe = succEntry(_) - or - exists(Split s | - s.hasEntry(_, cfe, _) - or - s.hasExit(_, cfe, _) - ) - } +/** + * DEPRECATED: Use `ElementNode` instead. + * + * A split node for a control flow element that belongs to a `finally` block. + */ +deprecated +class FinallySplitControlFlowNode extends ControlFlow::Nodes::ElementNode { + FinallySplitControlFlowNode() { + exists(ControlFlow::Internal::FinallySplitting::FinallySplitType type | + type = this.getASplit().(ControlFlow::Nodes::FinallySplit).getType() | + not type instanceof ControlFlow::SuccessorTypes::NormalSuccessor + ) + } - private predicate intraSplitsSucc(ControlFlowElement pred, ControlFlowElement succ) { - succ = succ(pred, _) and - not startsSplits(succ) - } + /** Gets the try statement that this `finally` node belongs to. */ + TryStmt getTryStmt() { + this.getElement() = ControlFlow::Internal::FinallySplitting::getAFinallyDescendant(result) + } +} - private predicate splitsBlockContains(ControlFlowElement start, ControlFlowElement cfe) = - fastTC(intraSplitsSucc/2)(start, cfe) +/** DEPRECATED: Use `ControlFlow::SuccessorType` instead. */ +deprecated +class ControlFlowEdgeType = ControlFlow::SuccessorType; - /** - * A block of control flow elements where the set of splits is guaranteed - * to remain unchanged, represented by the first element in the block. - */ - class SameSplitsBlock extends ControlFlowElement { - SameSplitsBlock() { - startsSplits(this) - } +/** DEPRECATED: Use `ControlFlow::NormalSuccessor` instead. */ +deprecated +class ControlFlowEdgeSuccessor = ControlFlow::SuccessorTypes::NormalSuccessor; - /** Gets a control flow element in this block. */ - ControlFlowElement getAnElement() { - splitsBlockContains(this, result) - or - result = this - } +/** DEPRECATED: Use `ControlFlow::ConditionalSuccessor` instead. */ +deprecated +class ControlFlowEdgeConditional = ControlFlow::SuccessorTypes::ConditionalSuccessor; - /** Gets a successor block, where the splits may be different. */ - SameSplitsBlock getASuccessor(Splits predSplits, Splits succSplits) { - exists(ControlFlowElement pred | - pred = this.getAnElement() | - succSplits(pred, predSplits, result, succSplits, _) - ) - } +/** DEPRECATED: Use `ControlFlow::BooleanSuccessor` instead. */ +deprecated +class ControlFlowEdgeBoolean = ControlFlow::SuccessorTypes::BooleanSuccessor; - /** - * Holds if the elements of this block are reachable from a callable entry - * point, with the splits `splits`. - */ - predicate isReachable(Splits splits) { - // Base case - succEntrySplits(_, this, splits, _) - or - // Recursive case - exists(SameSplitsBlock pred, Splits predSplits | - pred.isReachable(predSplits) | - this = pred.getASuccessor(predSplits, splits) - ) - } - } - } +/** DEPRECATED: Use `ControlFlow::NullnessSuccessor` instead. */ +deprecated +class ControlFlowEdgeNullness = ControlFlow::SuccessorTypes::NullnessSuccessor; - private cached module Cached { - cached - newtype TSplit = - TFinallySplit(FinallySplitting::FinallySplitType type) - or - TExceptionHandlerSplit(ExceptionClass ec) +/** DEPRECATED: Use `ControlFlow::MatchingSuccessor` instead. */ +deprecated +class ControlFlowEdgeMatching = ControlFlow::SuccessorTypes::MatchingSuccessor; - cached - newtype TSplits = - TSplitsNil() - or - TSplitsCons(Split head, Splits tail) { - exists(int rnk, ControlFlowElement cfe | - rnk = getSplitRank(head, cfe) | - rnk = 1 and - tail = TSplitsNil() - or - exists(Split tailHead | - tail = TSplitsCons(tailHead, _) | - rnk - 1 = getSplitRank(tailHead, cfe) - ) - ) - } +/** DEPRECATED: Use `ControlFlow::EmptinessSuccessor` instead. */ +deprecated +class ControlFlowEdgeEmptiness = ControlFlow::SuccessorTypes::EmptinessSuccessor; - /** - * Internal representation of control flow nodes in the control flow graph. - * The control flow graph is pruned for unreachable nodes. - */ - cached - newtype TControlFlowNode = - TCallableEntry(Callable c) { - succEntrySplits(c, _, _, _) - } - or - TCallableExit(Callable c) { - exists(Reachability::SameSplitsBlock b | - b.isReachable(_) | - succExitSplits(b.getAnElement(), _, c, _) - ) - } - or - TNode(ControlFlowElement cfe, Splits splits) { - exists(Reachability::SameSplitsBlock b | - b.isReachable(splits) | - cfe = b.getAnElement() - ) - } +/** DEPRECATED: Use `ControlFlow::ReturnSuccessor` instead. */ +deprecated +class ControlFlowEdgeReturn = ControlFlow::SuccessorTypes::ReturnSuccessor; - /** Internal representation of types of control flow. */ - cached - newtype TControlFlowEdgeType = - TSuccessorEdge() - or - TBooleanEdge(boolean b) { - b = true or b = false - } - or - TNullnessEdge(boolean isNull) { - isNull = true or isNull = false - } - or - TMatchingEdge(boolean isMatch) { - isMatch = true or isMatch = false - } - or - TEmptinessEdge(boolean isEmpty) { - isEmpty = true or isEmpty = false - } - or - TReturnEdge() - or - TBreakEdge() - or - TContinueEdge() - or - TGotoLabelEdge(GotoLabelStmt goto) - or - TGotoCaseEdge(GotoCaseStmt goto) - or - TGotoDefaultEdge() - or - TExceptionEdge(ExceptionClass ec) { - exists(ThrowCompletion c | c.getExceptionClass() = ec) - } +/** DEPRECATED: Use `ControlFlow::BreakSuccessor` instead. */ +deprecated +class ControlFlowEdgeBreak = ControlFlow::SuccessorTypes::BreakSuccessor; - /** Gets a successor node of a given flow type, if any. */ - cached - ControlFlowNode getASuccessorByType(ControlFlowNode pred, ControlFlowEdgeType t) { - // Callable entry node -> callable body - exists(ControlFlowElement succElement, Splits succSplits | - result = TNode(succElement, succSplits) | - succEntrySplits(pred.(CallableEntryNode).getCallable(), succElement, succSplits, t) - ) - or - exists(ControlFlowElement predElement, Splits predSplits | - pred = TNode(predElement, predSplits) | - // Element node -> callable exit - succExitSplits(predElement, predSplits, result.(CallableExitNode).getCallable(), t) - or - // Element node -> element node - exists(ControlFlowElement succElement, Splits succSplits | - result = TNode(succElement, succSplits) | - succSplits(predElement, predSplits, succElement, succSplits, t) - ) - ) - } +/** DEPRECATED: Use `ControlFlow::ContinueSuccessor` instead. */ +deprecated +class ControlFlowEdgeContinue = ControlFlow::SuccessorTypes::ContinueSuccessor; - /** - * Gets a first control flow element executed within `cfe`. - */ - cached - ControlFlowElement getAControlFlowEntryNode(ControlFlowElement cfe) { - result = first(cfe) - } +/** DEPRECATED: Use `ControlFlow::GotoLabelSuccessor` instead. */ +deprecated +class ControlFlowEdgeGotoLabel = ControlFlow::SuccessorTypes::GotoLabelSuccessor; - /** - * Gets a potential last control flow element executed within `cfe`. - */ - cached - ControlFlowElement getAControlFlowExitNode(ControlFlowElement cfe) { - result = last(cfe, _) - } - } - import Cached -} -private import Internal +/** DEPRECATED: Use `ControlFlow::GotoCaseSuccessor` instead. */ +deprecated +class ControlFlowEdgeGotoCase = ControlFlow::SuccessorTypes::GotoCaseSuccessor; + +/** DEPRECATED: Use `ControlFlow::GotoDefaultSuccessor` instead. */ +deprecated +class ControlFlowEdgeGotoDefault = ControlFlow::SuccessorTypes::GotoDefaultSuccessor; + +/** DEPRECATED: Use `ControlFlow::ExceptionSuccessor` instead. */ +deprecated +class ControlFlowEdgeException = ControlFlow::SuccessorTypes::ExceptionSuccessor; diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll index f9b9774af9e1..8dcc683f5c30 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll @@ -184,7 +184,7 @@ class NullGuardedExpr extends AccessOrCallExpr { // Call to `string.IsNullOrEmpty()` exists(MethodCall mc | mc = cond and - mc.getTarget() = getSystemStringClass().getIsNullOrEmptyMethod() and + mc.getTarget() = any(SystemStringClass c).getIsNullOrEmptyMethod() and mc.getArgument(0) = sub and b = false ) diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/Nullness.qll b/csharp/ql/src/semmle/code/csharp/dataflow/Nullness.qll index 65f0791f351c..031160733860 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/Nullness.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/Nullness.qll @@ -18,12 +18,10 @@ */ import csharp -private import ControlFlowGraph private import semmle.code.csharp.commons.Assertions private import semmle.code.csharp.commons.ComparisonTest private import semmle.code.csharp.controlflow.Guards private import semmle.code.csharp.dataflow.SSA -private import semmle.code.csharp.controlflow.ControlFlowGraph private import semmle.code.csharp.frameworks.System /** An expression that may be `null`. */ @@ -412,7 +410,7 @@ private Expr failureIsNonNullTest(LocalScopeVariable var) { * Gets an immediate successor node of the conditional node `cfgnode` where * the condition implies that the variable `var` is `null`. */ -private ControlFlowNode nullBranchKill(LocalScopeVariable var, ControlFlowNode cfgnode) { +private ControlFlow::Node nullBranchKill(LocalScopeVariable var, ControlFlow::Node cfgnode) { (cfgnode.getElement() = nullTest(var) and result = cfgnode.getATrueSuccessor()) or (cfgnode.getElement() = failureIsNullTest(var) and result = cfgnode.getAFalseSuccessor()) @@ -422,17 +420,17 @@ private ControlFlowNode nullBranchKill(LocalScopeVariable var, ControlFlowNode c * Gets an immediate successor node of the conditional node `cfgnode` where * the condition implies that the variable `var` is non-`null`. */ -private ControlFlowNode nonNullBranchKill(LocalScopeVariable var, ControlFlowNode cfgnode) { +private ControlFlow::Node nonNullBranchKill(LocalScopeVariable var, ControlFlow::Node cfgnode) { (cfgnode.getElement() = nonNullTest(var) and result = cfgnode.getATrueSuccessor()) or (cfgnode.getElement() = failureIsNonNullTest(var) and result = cfgnode.getAFalseSuccessor()) } /** Gets a node where the variable `var` may be `null`. */ -ControlFlowNode maybeNullNode(LocalScopeVariable var) { +ControlFlow::Node maybeNullNode(LocalScopeVariable var) { result = nullDef(var).getAControlFlowNode().getASuccessor() or - exists(ControlFlowNode mid | + exists(ControlFlow::Node mid | mid = maybeNullNode(var) and not mid.getElement() = nonNullDef(var) and mid.getASuccessor() = result and @@ -441,10 +439,10 @@ ControlFlowNode maybeNullNode(LocalScopeVariable var) { } /** Gets a node where the variable `var` may be non-`null`. */ -ControlFlowNode maybeNonNullNode(LocalScopeVariable var) { +ControlFlow::Node maybeNonNullNode(LocalScopeVariable var) { result = nonNullDef(var).getAControlFlowNode().getASuccessor() or - exists(ControlFlowNode mid | + exists(ControlFlow::Node mid | mid = maybeNonNullNode(var) and not mid.getElement() = nullDef(var) and mid.getASuccessor() = result and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll b/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll index 54a111fe935a..09f1fa9f073f 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll @@ -5,8 +5,7 @@ import csharp module Ssa { - class BasicBlock = ControlFlowGraph::BasicBlock; - class ControlFlowNode = ControlFlowGraph::ControlFlowNode; + class BasicBlock = ControlFlow::BasicBlock; private module SourceVariableImpl { private import AssignableDefinitions @@ -207,7 +206,7 @@ module Ssa { * Holds if the `i`th node `node` of basic block `bb` reads source variable `v`. * The read at `node` is of kind `rk`. */ - predicate variableRead(BasicBlock bb, int i, SourceVariable v, ControlFlowNode node, ReadKind rk) { + predicate variableRead(BasicBlock bb, int i, SourceVariable v, ControlFlow::Node node, ReadKind rk) { v.getAnAccess().(AssignableRead) = node.getElement() and node = bb.getNode(i) and rk = ActualRead() @@ -222,7 +221,7 @@ module Ssa { rk = RefReadBeforeWrite() } - private predicate outRefExitRead(ControlFlowGraph::ExitBasicBlock ebb, int i, LocalScopeSourceVariable v, ControlFlowGraph::CallableExitNode node) { + private predicate outRefExitRead(ControlFlow::BasicBlocks::ExitBlock ebb, int i, LocalScopeSourceVariable v, ControlFlow::Nodes::ExitNode node) { exists(LocalScopeVariable lsv | lsv = v.getAssignable() and ebb.getNode(i) = node and @@ -231,7 +230,7 @@ module Ssa { ) } - private predicate capturedVarExitRead(ControlFlowGraph::ExitBasicBlock ebb, int i, LocalScopeSourceVariable v, ControlFlowGraph::CallableExitNode node) { + private predicate capturedVarExitRead(ControlFlow::BasicBlocks::ExitBlock ebb, int i, LocalScopeSourceVariable v, ControlFlow::Nodes::ExitNode node) { exists(BasicBlock bb | variableDefinition(bb, _, v, _) | ebb.getNode(i) = node and @@ -240,7 +239,7 @@ module Ssa { ) } - private predicate refReadBeforeWrite(BasicBlock bb, int i, LocalScopeSourceVariable v, ControlFlowNode node) { + private predicate refReadBeforeWrite(BasicBlock bb, int i, LocalScopeSourceVariable v, ControlFlow::Node node) { exists(AssignableDefinitions::AssignmentDefinition def, LocalVariable lv | def.getTarget() = lv and lv.isRef() and @@ -692,7 +691,7 @@ module Ssa { * same basic block without crossing another SSA definition of `v`. * The read at `node` is of kind `rk`. */ - private predicate ssaDefReachesReadWithinBlock(TrackedVar v, TrackedDefinition def, ControlFlowNode read, ReadKind rk) { + private predicate ssaDefReachesReadWithinBlock(TrackedVar v, TrackedDefinition def, ControlFlow::Node read, ReadKind rk) { exists(BasicBlock bb, int rankix, int i | ssaDefReachesRank(bb, def, rankix, v) and rankix = ssaRefRank(bb, i, v, SsaRead()) and @@ -873,7 +872,7 @@ module Ssa { * The read at `node` is of kind `rk`. */ cached - predicate ssaDefReachesRead(TrackedVar v, TrackedDefinition def, ControlFlowNode read, ReadKind rk) { + predicate ssaDefReachesRead(TrackedVar v, TrackedDefinition def, ControlFlow::Node read, ReadKind rk) { ssaDefReachesReadWithinBlock(v, def, read, rk) or exists(BasicBlock bb | @@ -1615,7 +1614,7 @@ module Ssa { * `c` may read the value of the captured variable. */ private predicate capturerReads(Callable c, LocalScopeVariable v) { - exists(ControlFlowGraph::EntryBasicBlock ebb, LocalScopeSourceVariable lssv | + exists(ControlFlow::BasicBlocks::EntryBlock ebb, LocalScopeSourceVariable lssv | liveAtEntry(ebb, lssv, _) | v = lssv.getAssignable() and c = ebb.getCallable() and @@ -1873,7 +1872,7 @@ module Ssa { ) } or - TSsaImplicitEntryDef(TrackedVar v, ControlFlowGraph::EntryBasicBlock ebb) { + TSsaImplicitEntryDef(TrackedVar v, ControlFlow::BasicBlocks::EntryBlock ebb) { liveAtEntry(ebb, v, _) and exists(Callable c | @@ -1921,7 +1920,7 @@ module Ssa { bb.getNode(i + 1) = v.getAnAccess().(AssignableRead).getAControlFlowNode() } or - TPhiNode(TrackedVar v, ControlFlowGraph::JoinBlock bb) { + TPhiNode(TrackedVar v, ControlFlow::BasicBlocks::JoinBlock bb) { liveAtEntry(bb, v, _) and exists(BasicBlock bb1, Definition def | @@ -2067,7 +2066,7 @@ module Ssa { * - The reads of `this.Field` on lines 10 and 11 can be reached from the phi * node between lines 9 and 10. */ - AssignableRead getAReadAtNode(ControlFlowNode cfn) { + AssignableRead getAReadAtNode(ControlFlow::Node cfn) { ssaDefReachesRead(_, this, cfn, _) and result.getAControlFlowNode() = cfn } @@ -2250,7 +2249,7 @@ module Ssa { * parameter may remain unchanged throughout the rest of the enclosing callable. */ predicate isLiveOutRefParameterDefinition(Parameter p) { - exists(Definition def, ControlFlowNode read, SourceVariable v | + exists(Definition def, ControlFlow::Node read, SourceVariable v | this = def.getAnUltimateDefinition() | ssaDefReachesRead(v, def, read, OutRefExitRead()) and v.getAssignable() = p @@ -2371,7 +2370,7 @@ module Ssa { class ImplicitEntryDefinition extends ImplicitDefinition, TSsaImplicitEntryDef { /** Gets the callable that this entry definition belongs to. */ Callable getCallable() { - exists(ControlFlowGraph::EntryBasicBlock ebb | + exists(ControlFlow::BasicBlocks::EntryBlock ebb | this = TSsaImplicitEntryDef(_, ebb) and result = ebb.getCallable() ) @@ -2533,7 +2532,7 @@ module Ssa { * does not exist in the source program. */ override Location getLocation() { - exists(ControlFlowGraph::JoinBlock bb | + exists(ControlFlow::BasicBlocks::JoinBlock bb | this = TPhiNode(_, bb) and result = bb.getFirstNode().getLocation() ) diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/BaseSSA.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/BaseSSA.qll index cadb30f9d303..9f005557d92a 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/BaseSSA.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/BaseSSA.qll @@ -6,7 +6,7 @@ import csharp * Provides a simple SSA implementation for local scope variables. */ module BaseSsa { - private import ControlFlowGraph + private import ControlFlow private import AssignableDefinitions private class SimpleLocalScopeVariable extends LocalScopeVariable { diff --git a/csharp/ql/src/semmle/code/csharp/exprs/ArithmeticOperation.qll b/csharp/ql/src/semmle/code/csharp/exprs/ArithmeticOperation.qll index 96544057ee6c..5fcb43a18972 100644 --- a/csharp/ql/src/semmle/code/csharp/exprs/ArithmeticOperation.qll +++ b/csharp/ql/src/semmle/code/csharp/exprs/ArithmeticOperation.qll @@ -11,7 +11,7 @@ import Expr * (`BinaryArithmeticOperation`). */ class ArithmeticOperation extends Operation, @arith_op_expr { - string getOperator() { none() } + override string getOperator() { none() } } /** diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll index 2de820117f4b..a2a98faedcb0 100644 --- a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll +++ b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll @@ -38,10 +38,9 @@ class Call extends DotNet::Call, Expr, @call { * Use `getARuntimeTarget()` instead to get a potential run-time target (will * include `B.M` in the example above). */ - Callable getTarget() { none() } + override Callable getTarget() { none() } - /** Gets the `i`th argument of this call. */ - Expr getArgument(int i) { + override Expr getArgument(int i) { result = this.getChild(i) and i >= 0 } @@ -49,8 +48,7 @@ class Call extends DotNet::Call, Expr, @call { result = this.getArgument(i) } - /** Gets an argument of this call. */ - Expr getAnArgument() { + override Expr getAnArgument() { result = getArgument(_) } @@ -189,7 +187,7 @@ class Call extends DotNet::Call, Expr, @call { * - Line 16: There is no static target (delegate call) but the delegate `i => { }` (line * 20) is a run-time target. */ - Callable getARuntimeTarget() { + override Callable getARuntimeTarget() { exists(DispatchCall dc | dc.getCall() = this | result = dc.getADynamicTarget()) } diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/Format.qll b/csharp/ql/src/semmle/code/csharp/frameworks/Format.qll index e71188095b1e..3679cdbd1bbf 100644 --- a/csharp/ql/src/semmle/code/csharp/frameworks/Format.qll +++ b/csharp/ql/src/semmle/code/csharp/frameworks/Format.qll @@ -189,11 +189,11 @@ private module FormatFlow { private class FormatConfiguration extends DataFlow::Configuration { FormatConfiguration() { this = "format" } - predicate isSource(DataFlow::Node n) { + override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof StringLiteral } - predicate isSink(DataFlow::Node n) { + override predicate isSink(DataFlow::Node n) { exists(FormatCall c | n.asExpr() = c.getFormatExpr()) } } diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/system/Web.qll b/csharp/ql/src/semmle/code/csharp/frameworks/system/Web.qll index a15c6e36539c..fc0d2e3b8f69 100644 --- a/csharp/ql/src/semmle/code/csharp/frameworks/system/Web.qll +++ b/csharp/ql/src/semmle/code/csharp/frameworks/system/Web.qll @@ -77,7 +77,7 @@ class SystemWebHttpRequestClass extends SystemWebClass { and result.hasName("Url") and - result.getType() = getSystemUriClass() + result.getType() instanceof SystemUriClass } } diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/system/runtime/InteropServices.qll b/csharp/ql/src/semmle/code/csharp/frameworks/system/runtime/InteropServices.qll index 1f50fbee7926..a2ce047a0c90 100644 --- a/csharp/ql/src/semmle/code/csharp/frameworks/system/runtime/InteropServices.qll +++ b/csharp/ql/src/semmle/code/csharp/frameworks/system/runtime/InteropServices.qll @@ -6,7 +6,7 @@ private import semmle.code.csharp.frameworks.system.Runtime /** The `System.Runtime.InteropServices` namespace. */ class SystemRuntimeInteropServicesNamespace extends Namespace { SystemRuntimeInteropServicesNamespace() { - this.getParentNamespace() = getSystemRuntimeNamespace() and + this.getParentNamespace() instanceof SystemRuntimeNamespace and this.hasName("InteropServices") } } diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/MissingXMLValidation.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/MissingXMLValidation.qll index aaa90a7e15cc..eb2be3bca48b 100644 --- a/csharp/ql/src/semmle/code/csharp/security/dataflow/MissingXMLValidation.qll +++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/MissingXMLValidation.qll @@ -72,7 +72,7 @@ module MissingXMLValidation { this.getExpr() = createCall.getArgumentForName("input") } - string getReason() { + override string getReason() { // No settings = no Schema validation result = "there is no 'XmlReaderSettings' instance specifying schema validation." and not exists(createCall.getSettings()) or /* diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/XMLEntityInjection.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/XMLEntityInjection.qll index aaa19186b8dd..3df588f4749f 100644 --- a/csharp/ql/src/semmle/code/csharp/security/dataflow/XMLEntityInjection.qll +++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/XMLEntityInjection.qll @@ -50,7 +50,7 @@ module XMLEntityInjection { ).getAnArgument() } - string getReason() { + override string getReason() { exists(InsecureXML::InsecureXmlProcessing r | r.isUnsafe(result) | this.getExpr() = r.getAnArgument()) } } diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll index 151cc68c71c4..36261409bacc 100644 --- a/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll +++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll @@ -451,7 +451,7 @@ module XSS { this.getExpr() = aspWrittenValue(inline) } - string explanation() { + override string explanation() { result = "member is [[\"accessed inline\"|\"" +makeUrl(inline.getLocation())+ "\"]] in an ASPX page" } } diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql index 8c6239efe6bc..7f0035c2ea09 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql @@ -1,6 +1,5 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph -from ConditionBlock cb, BasicBlock controlled, boolean testIsTrue +from ControlFlow::BasicBlocks::ConditionBlock cb, ControlFlow::BasicBlock controlled, boolean testIsTrue where cb.controls(controlled, testIsTrue) select cb.getLastNode(), controlled.getFirstNode(), testIsTrue diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.ql b/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.ql index 9c933298ed9d..cf21cf130be0 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.ql @@ -1,12 +1,11 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph -ControlFlowNode successor(ControlFlowNode node, boolean kind) { +ControlFlow::Node successor(ControlFlow::Node node, boolean kind) { (kind = true and result = node.getATrueSuccessor()) or (kind = false and result = node.getAFalseSuccessor()) } -from ControlFlowNode node, ControlFlowNode successor, Location nl, Location sl, boolean kind +from ControlFlow::Node node, ControlFlow::Node successor, Location nl, Location sl, boolean kind where successor = successor(node, kind) and nl = node.getLocation() and sl = successor.getLocation() diff --git a/csharp/ql/test/library-tests/controlflow/graph/Dominance.ql b/csharp/ql/test/library-tests/controlflow/graph/Dominance.ql index 9f04f46a9051..e1be9ad496cf 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Dominance.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/Dominance.ql @@ -1,7 +1,6 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph -from ControlFlowNode dom, ControlFlowNode node, string s +from ControlFlow::Node dom, ControlFlow::Node node, string s where dom.strictlyDominates(node) and dom.getASuccessor() = node and s = "pre" or diff --git a/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.ql b/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.ql index 8c7e83ed4cc8..5c897e08a2bd 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.ql @@ -1,8 +1,7 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph query predicate edges(ControlFlowElement node, ControlFlowElement successor, string attr, string val) { - exists(ControlFlowEdgeType t | + exists(ControlFlow::SuccessorType t | successor = node.getAControlFlowNode().getASuccessorByType(t).getElement() | attr = "semmle.label" and val = t.toString() diff --git a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql index d21b0f833d4a..63cbfd32afc7 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.ql @@ -1,5 +1,5 @@ import csharp -import ControlFlowGraph::Internal +import ControlFlow::Internal from ControlFlowElement cfe select cfe, first(cfe) diff --git a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql index e1a276add434..0a3b97914464 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.ql @@ -1,5 +1,5 @@ import csharp -import ControlFlowGraph::Internal +import ControlFlow::Internal private import semmle.code.csharp.controlflow.Completion from ControlFlowElement cfe, Completion c diff --git a/csharp/ql/test/library-tests/controlflow/graph/FinallyNode.ql b/csharp/ql/test/library-tests/controlflow/graph/FinallyNode.ql index 73f6413128b8..c8d15e063472 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/FinallyNode.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/FinallyNode.ql @@ -1,12 +1,13 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph +import ControlFlow import Internal +import Nodes -class MyFinallySplitControlFlowNode extends ControlFlowElementNode { +class MyFinallySplitControlFlowNode extends ElementNode { MyFinallySplitControlFlowNode() { exists(FinallySplitting::FinallySplitType type | type = this.getASplit().(FinallySplit).getType() | - not type instanceof ControlFlowEdgeSuccessor + not type instanceof SuccessorTypes::NormalSuccessor ) } diff --git a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.ql b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.ql index 1d0e14b7a913..82ce415accd9 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.ql @@ -1,8 +1,7 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph -query predicate edges(ControlFlowNode node, ControlFlowNode successor, string attr, string val) { - exists(ControlFlowEdgeType t | +query predicate edges(ControlFlow::Node node, ControlFlow::Node successor, string attr, string val) { + exists(ControlFlow::SuccessorType t | successor = node.getASuccessorByType(t) | attr = "semmle.label" and val = t.toString() diff --git a/csharp/ql/test/library-tests/csharp7/IsFlow.ql b/csharp/ql/test/library-tests/csharp7/IsFlow.ql index 236a3346eba4..e5ca92054178 100644 --- a/csharp/ql/test/library-tests/csharp7/IsFlow.ql +++ b/csharp/ql/test/library-tests/csharp7/IsFlow.ql @@ -1,8 +1,7 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph -query predicate edges(ControlFlowNode n1, ControlFlowNode n2, string attr, string val) { - exists(SwitchStmt switch, ControlFlowEdgeType t | +query predicate edges(ControlFlow::Node n1, ControlFlow::Node n2, string attr, string val) { + exists(SwitchStmt switch, ControlFlow::SuccessorType t | switch.getAControlFlowNode().getASuccessor*()=n1 | n2 = n1.getASuccessorByType(t) and attr = "semmle.label" and diff --git a/csharp/ql/test/library-tests/dataflow/defuse/defUseEquivalence.ql b/csharp/ql/test/library-tests/dataflow/defuse/defUseEquivalence.ql index 5aa8cf516c77..4f3fc8532c2d 100644 --- a/csharp/ql/test/library-tests/dataflow/defuse/defUseEquivalence.ql +++ b/csharp/ql/test/library-tests/dataflow/defuse/defUseEquivalence.ql @@ -1,11 +1,10 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph /** "Naive" def-use implementation. */ -predicate defReaches(AssignableDefinition def, LocalScopeVariable v, ControlFlowNode cfn) { +predicate defReaches(AssignableDefinition def, LocalScopeVariable v, ControlFlow::Node cfn) { def.getTarget() = v and cfn = def.getAControlFlowNode().getASuccessor() or - exists(ControlFlowNode mid | + exists(ControlFlow::Node mid | defReaches(def, v, mid) | not mid = any(AssignableDefinition ad | ad.getTarget() = v and ad.isCertain()).getAControlFlowNode() and cfn = mid.getASuccessor() diff --git a/csharp/ql/test/library-tests/dataflow/defuse/parameterUseEquivalence.ql b/csharp/ql/test/library-tests/dataflow/defuse/parameterUseEquivalence.ql index 761c3d667755..837d33933ce7 100644 --- a/csharp/ql/test/library-tests/dataflow/defuse/parameterUseEquivalence.ql +++ b/csharp/ql/test/library-tests/dataflow/defuse/parameterUseEquivalence.ql @@ -1,11 +1,10 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph /** "Naive" parameter-use implementation. */ -predicate parameterReaches(Parameter p, ControlFlowNode cfn) { +predicate parameterReaches(Parameter p, ControlFlow::Node cfn) { cfn = p.getCallable().getEntryPoint().getASuccessor() or - exists(ControlFlowNode mid | + exists(ControlFlow::Node mid | parameterReaches(p, mid) | not mid = any(AssignableDefinition ad | ad.getTarget() = p and ad.isCertain()).getAControlFlowNode() and cfn = mid.getASuccessor() diff --git a/csharp/ql/test/library-tests/dataflow/defuse/useUseEquivalence.ql b/csharp/ql/test/library-tests/dataflow/defuse/useUseEquivalence.ql index 74dc4615c96a..f70c1a54f5a1 100644 --- a/csharp/ql/test/library-tests/dataflow/defuse/useUseEquivalence.ql +++ b/csharp/ql/test/library-tests/dataflow/defuse/useUseEquivalence.ql @@ -1,11 +1,10 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph /** "Naive" use-use implementation. */ -predicate useReaches(LocalScopeVariableRead read, LocalScopeVariable v, ControlFlowNode cfn) { +predicate useReaches(LocalScopeVariableRead read, LocalScopeVariable v, ControlFlow::Node cfn) { read.getTarget() = v and cfn = read.getAControlFlowNode().getASuccessor() or - exists(ControlFlowNode mid | + exists(ControlFlow::Node mid | useReaches(read, v, mid) | not mid = any(AssignableDefinition ad | ad.getTarget() = v and ad.isCertain()).getAControlFlowNode() and cfn = mid.getASuccessor() @@ -29,7 +28,7 @@ private TLocalScopeVariableReadOrSsaDef getANextReadOrDef(TLocalScopeVariableRea result = TLocalScopeVariableRead(read.getANextRead()) or not exists(read.getANextRead()) and - exists(Ssa::Definition ssaDef, Ssa::PseudoDefinition pseudoDef, ControlFlowNode cfn, BasicBlock bb, int i | + exists(Ssa::Definition ssaDef, Ssa::PseudoDefinition pseudoDef, ControlFlow::Node cfn, ControlFlow::BasicBlock bb, int i | ssaDef.getARead() = read | pseudoDef.getAnInput() = ssaDef and pseudoDef.definesAt(bb, i) and diff --git a/csharp/ql/test/library-tests/dataflow/ssa-large/countssa.ql b/csharp/ql/test/library-tests/dataflow/ssa-large/countssa.ql index cc328e3f62e4..03dc84e8adce 100644 --- a/csharp/ql/test/library-tests/dataflow/ssa-large/countssa.ql +++ b/csharp/ql/test/library-tests/dataflow/ssa-large/countssa.ql @@ -3,5 +3,5 @@ import csharp from int uses, int live where uses = strictcount(Ssa::ExplicitDefinition ssa, AssignableRead read | read = ssa.getARead()) and -live = strictcount(Ssa::ExplicitDefinition ssa, ControlFlowGraph::BasicBlock bb | ssa.isLiveAtEndOfBlock(bb)) +live = strictcount(Ssa::ExplicitDefinition ssa, ControlFlow::BasicBlock bb | ssa.isLiveAtEndOfBlock(bb)) select uses, live diff --git a/csharp/ql/test/library-tests/goto/Goto1.ql b/csharp/ql/test/library-tests/goto/Goto1.ql index 1d0e14b7a913..82ce415accd9 100644 --- a/csharp/ql/test/library-tests/goto/Goto1.ql +++ b/csharp/ql/test/library-tests/goto/Goto1.ql @@ -1,8 +1,7 @@ import csharp -import semmle.code.csharp.controlflow.ControlFlowGraph -query predicate edges(ControlFlowNode node, ControlFlowNode successor, string attr, string val) { - exists(ControlFlowEdgeType t | +query predicate edges(ControlFlow::Node node, ControlFlow::Node successor, string attr, string val) { + exists(ControlFlow::SuccessorType t | successor = node.getASuccessorByType(t) | attr = "semmle.label" and val = t.toString() From 82b2c00c19c0562b32ba5032e92f9278929f12bb Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 4 Sep 2018 16:13:18 +0200 Subject: [PATCH 25/50] C#: Add change note --- change-notes/1.18/analysis-csharp.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/change-notes/1.18/analysis-csharp.md b/change-notes/1.18/analysis-csharp.md index 67a6eaad4aa2..0aaaea3a748e 100644 --- a/change-notes/1.18/analysis-csharp.md +++ b/change-notes/1.18/analysis-csharp.md @@ -40,4 +40,22 @@ ## Changes to QL libraries -* A new non-member predicate `mayBeDisposed()` can be used to determine if a variable is potentially disposed inside a library. It will analyse the CIL code in the library to determine this. \ No newline at end of file +* A new non-member predicate `mayBeDisposed()` can be used to determine if a variable is potentially disposed inside a library. It will analyse the CIL code in the library to determine this. +* Several control flow graph entities have been renamed (the old names still exist for backwards compatibility): + - `ControlFlowNode` has been renamed to `ControlFlow::Node`. + - `CallableEntryNode` has been renamed to `ControlFlow::Nodes::EntryNode`. + - `CallableExitNode` has been renamed to `ControlFlow::Nodes::ExitNode`. + - `ControlFlowEdgeType` has been renamed to `ControlFlow::SuccessorType`. + - `ControlFlowEdgeSuccessor` has been renamed to `ControlFlow::SuccessorTypes::NormalSuccessor`. + - `ControlFlowEdgeConditional has been renamed to ControlFlow::SuccessorTypes::ConditionalSuccessor`. + - `ControlFlowEdgeBoolean` has been renamed to `ControlFlow::SuccessorTypes::BooleanSuccessor`. + - `ControlFlowEdgeNullness` has been renamed to `ControlFlow::SuccessorTypes::NullnessSuccessor`. + - `ControlFlowEdgeMatching` has been renamed to `ControlFlow::SuccessorTypes::MatchingSuccessor`. + - `ControlFlowEdgeEmptiness` has been renamed to `ControlFlow::SuccessorTypes::EmptinessSuccessor`. + - `ControlFlowEdgeReturn` has been renamed to `ControlFlow::SuccessorTypes::ReturnSuccessor`. + - `ControlFlowEdgeBreak` has been renamed to `ControlFlow::SuccessorTypes::BreakSuccessor`. + - `ControlFlowEdgeContinue` has been renamed to `ControlFlow::SuccessorTypes::ContinueSuccessor`. + - `ControlFlowEdgeGotoLabel` has been renamed to `ControlFlow::SuccessorTypes::GotoLabelSuccessor`. + - `ControlFlowEdgeGotoCase` has been renamed to `ControlFlow::SuccessorTypes::GotoCaseSuccessor`. + - `ControlFlowEdgeGotoDefault` has been renamed to `ControlFlow::SuccessorTypes::GotoDefaultSuccessor`. + - `ControlFlowEdgeException` has been renamed to `ControlFlow::SuccessorTypes::ExceptionSuccessor`. From 061b3d40eacfbd6b9ea90673169ec0e8a099b8e0 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 4 Sep 2018 19:02:48 +0200 Subject: [PATCH 26/50] C#: Fix typos in change note --- change-notes/1.18/analysis-csharp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change-notes/1.18/analysis-csharp.md b/change-notes/1.18/analysis-csharp.md index 0aaaea3a748e..303c950cf55d 100644 --- a/change-notes/1.18/analysis-csharp.md +++ b/change-notes/1.18/analysis-csharp.md @@ -47,7 +47,7 @@ - `CallableExitNode` has been renamed to `ControlFlow::Nodes::ExitNode`. - `ControlFlowEdgeType` has been renamed to `ControlFlow::SuccessorType`. - `ControlFlowEdgeSuccessor` has been renamed to `ControlFlow::SuccessorTypes::NormalSuccessor`. - - `ControlFlowEdgeConditional has been renamed to ControlFlow::SuccessorTypes::ConditionalSuccessor`. + - `ControlFlowEdgeConditional` has been renamed to `ControlFlow::SuccessorTypes::ConditionalSuccessor`. - `ControlFlowEdgeBoolean` has been renamed to `ControlFlow::SuccessorTypes::BooleanSuccessor`. - `ControlFlowEdgeNullness` has been renamed to `ControlFlow::SuccessorTypes::NullnessSuccessor`. - `ControlFlowEdgeMatching` has been renamed to `ControlFlow::SuccessorTypes::MatchingSuccessor`. From 7a77740979c22e588bfb03e070f7d1fa59a655dd Mon Sep 17 00:00:00 2001 From: calum Date: Wed, 5 Sep 2018 16:18:53 +0100 Subject: [PATCH 27/50] C#: Extractor tests for - While statements - Object initializers --- .../test/library-tests/regressions/Program.cs | 25 +++++++++++++++++++ .../regressions/TypeMentions.expected | 7 ++++++ .../regressions/WhileIs.expected | 1 + .../test/library-tests/regressions/WhileIs.ql | 5 ++++ 4 files changed, 38 insertions(+) create mode 100644 csharp/ql/test/library-tests/regressions/WhileIs.expected create mode 100644 csharp/ql/test/library-tests/regressions/WhileIs.ql diff --git a/csharp/ql/test/library-tests/regressions/Program.cs b/csharp/ql/test/library-tests/regressions/Program.cs index 88f57263a30c..eb7b2541e561 100644 --- a/csharp/ql/test/library-tests/regressions/Program.cs +++ b/csharp/ql/test/library-tests/regressions/Program.cs @@ -81,3 +81,28 @@ int Test() return 0; } } + +class WhileIs +{ + void Test() + { + object x = null; + while(x is string s) + { + var y = s; + } + } +} + +class ObjectInitializerType +{ + struct Point + { + public object Name; + } + + void F() + { + new Point() { Name = "Bob" }; + } +} diff --git a/csharp/ql/test/library-tests/regressions/TypeMentions.expected b/csharp/ql/test/library-tests/regressions/TypeMentions.expected index 76bb248e4885..2133ff2707b9 100644 --- a/csharp/ql/test/library-tests/regressions/TypeMentions.expected +++ b/csharp/ql/test/library-tests/regressions/TypeMentions.expected @@ -39,3 +39,10 @@ | Program.cs:69:5:69:8 | Boolean | | Program.cs:69:16:69:18 | Int32 | | Program.cs:75:5:75:7 | Int32 | +| Program.cs:87:5:87:8 | Void | +| Program.cs:89:9:89:14 | Object | +| Program.cs:90:20:90:25 | String | +| Program.cs:92:13:92:15 | String | +| Program.cs:101:16:101:21 | Object | +| Program.cs:104:5:104:8 | Void | +| Program.cs:106:13:106:17 | Point | diff --git a/csharp/ql/test/library-tests/regressions/WhileIs.expected b/csharp/ql/test/library-tests/regressions/WhileIs.expected new file mode 100644 index 000000000000..7b9cb7710d88 --- /dev/null +++ b/csharp/ql/test/library-tests/regressions/WhileIs.expected @@ -0,0 +1 @@ +| Program.cs:92:21:92:21 | access to local variable s | diff --git a/csharp/ql/test/library-tests/regressions/WhileIs.ql b/csharp/ql/test/library-tests/regressions/WhileIs.ql new file mode 100644 index 000000000000..9380bc09d7e4 --- /dev/null +++ b/csharp/ql/test/library-tests/regressions/WhileIs.ql @@ -0,0 +1,5 @@ +import csharp + +from LocalVariable decl +where decl.getName() = "s" +select decl.getAnAccess() From 8c2d773866b30effeb68af9605ea3a646b477c45 Mon Sep 17 00:00:00 2001 From: calum Date: Wed, 5 Sep 2018 16:19:46 +0100 Subject: [PATCH 28/50] C#: Extractor test for `join ... into` --- .../ql/test/library-tests/linq/Linq1.expected | 20 ++++++++++++------- csharp/ql/test/library-tests/linq/queries.cs | 5 +++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/csharp/ql/test/library-tests/linq/Linq1.expected b/csharp/ql/test/library-tests/linq/Linq1.expected index 4ec221d61cfd..8cf5f10679ac 100644 --- a/csharp/ql/test/library-tests/linq/Linq1.expected +++ b/csharp/ql/test/library-tests/linq/Linq1.expected @@ -17,21 +17,21 @@ | queries.cs:18:21:21:28 | call to method Select | 1 | queries.cs:21:18:21:27 | (...) => ... | | queries.cs:25:11:25:21 | call to method SelectMany | 0 | queries.cs:24:11:24:25 | IList a = ... | | queries.cs:25:11:25:21 | call to method SelectMany | 1 | queries.cs:25:11:25:21 | IList b = ... | -| queries.cs:25:11:25:21 | call to method SelectMany | 2 | queries.cs:25:21:25:21 | access to local variable a | +| queries.cs:25:11:25:21 | call to method SelectMany | 3 | queries.cs:25:21:25:21 | access to local variable a | | queries.cs:26:11:26:26 | call to method Select | 0 | queries.cs:25:11:25:21 | call to method SelectMany | | queries.cs:26:11:26:26 | call to method Select | 1 | queries.cs:26:11:26:26 | Int32 next = ... | -| queries.cs:26:11:26:26 | call to method Select | 2 | queries.cs:26:22:26:26 | ... + ... | +| queries.cs:26:11:26:26 | call to method Select | 3 | queries.cs:26:22:26:26 | ... + ... | | queries.cs:27:11:27:42 | call to method Join | 0 | queries.cs:26:11:26:26 | call to method Select | | queries.cs:27:11:27:42 | call to method Join | 1 | queries.cs:27:11:27:42 | IList c = ... | -| queries.cs:27:11:27:42 | call to method Join | 2 | queries.cs:27:21:27:25 | access to local variable list1 | -| queries.cs:27:11:27:42 | call to method Join | 3 | queries.cs:27:30:27:33 | access to local variable next | -| queries.cs:27:11:27:42 | call to method Join | 4 | queries.cs:27:42:27:42 | access to local variable c | +| queries.cs:27:11:27:42 | call to method Join | 3 | queries.cs:27:21:27:25 | access to local variable list1 | +| queries.cs:27:11:27:42 | call to method Join | 4 | queries.cs:27:30:27:33 | access to local variable next | +| queries.cs:27:11:27:42 | call to method Join | 5 | queries.cs:27:42:27:42 | access to local variable c | | queries.cs:32:11:32:21 | call to method SelectMany | 0 | queries.cs:31:11:31:25 | IList a = ... | | queries.cs:32:11:32:21 | call to method SelectMany | 1 | queries.cs:32:11:32:21 | IList b = ... | -| queries.cs:32:11:32:21 | call to method SelectMany | 2 | queries.cs:32:21:32:21 | access to local variable a | +| queries.cs:32:11:32:21 | call to method SelectMany | 3 | queries.cs:32:21:32:21 | access to local variable a | | queries.cs:33:11:33:26 | call to method Select | 0 | queries.cs:32:11:32:21 | call to method SelectMany | | queries.cs:33:11:33:26 | call to method Select | 1 | queries.cs:33:11:33:26 | Int32 next = ... | -| queries.cs:33:11:33:26 | call to method Select | 2 | queries.cs:33:22:33:26 | ... + ... | +| queries.cs:33:11:33:26 | call to method Select | 3 | queries.cs:33:22:33:26 | ... + ... | | queries.cs:34:11:34:37 | call to method OrderByDescending | 0 | queries.cs:33:11:33:26 | call to method Select | | queries.cs:34:11:34:37 | call to method OrderByDescending | 1 | queries.cs:34:19:34:26 | ... * ... | | queries.cs:35:11:35:25 | call to method GroupBy | 0 | queries.cs:34:11:34:37 | call to method OrderByDescending | @@ -43,3 +43,9 @@ | queries.cs:47:11:47:18 | call to method Select | 1 | queries.cs:47:18:47:18 | access to local variable a | | queries.cs:51:11:51:18 | call to method Select | 0 | queries.cs:50:11:50:32 | String a = ... | | queries.cs:51:11:51:18 | call to method Select | 1 | queries.cs:51:18:51:18 | access to local variable a | +| queries.cs:55:11:55:49 | call to method GroupJoin | 0 | queries.cs:54:11:54:25 | Int32 a = ... | +| queries.cs:55:11:55:49 | call to method GroupJoin | 1 | queries.cs:55:11:55:49 | IList> c = ... | +| queries.cs:55:11:55:49 | call to method GroupJoin | 2 | queries.cs:55:11:55:49 | IList> d = ... | +| queries.cs:55:11:55:49 | call to method GroupJoin | 3 | queries.cs:55:21:55:25 | access to local variable list2 | +| queries.cs:55:11:55:49 | call to method GroupJoin | 4 | queries.cs:55:30:55:30 | access to local variable a | +| queries.cs:55:11:55:49 | call to method GroupJoin | 5 | queries.cs:55:39:55:42 | access to indexer | diff --git a/csharp/ql/test/library-tests/linq/queries.cs b/csharp/ql/test/library-tests/linq/queries.cs index 5030e86a2cb1..93ef2af3b85c 100644 --- a/csharp/ql/test/library-tests/linq/queries.cs +++ b/csharp/ql/test/library-tests/linq/queries.cs @@ -49,6 +49,11 @@ from a in list9 var list11 = from string a in list7 select a; + + var list12 = + from a in list1 + join c in list2 on a equals c[0] into d + select (a,d); } class A : System.Collections.IEnumerable From cff00506ba5d2f963fd04f09b43dbea52cfb746e Mon Sep 17 00:00:00 2001 From: calum Date: Wed, 5 Sep 2018 16:22:12 +0100 Subject: [PATCH 29/50] C#: Implementation of `case ... when ...:` which was not previously handled. Move `getCondition` to `CaseStmt`. Implement the CFG and tests. --- csharp/ql/src/semmle/code/csharp/Stmt.qll | 53 ++++---- .../code/csharp/controlflow/Completion.qll | 2 +- .../controlflow/graph/BasicBlock.expected | 8 ++ .../graph/BasicBlockDominance.expected | 35 +++++ .../controlflow/graph/ConditionBlock.expected | 2 + .../graph/ConditionalFlow.expected | 4 + .../controlflow/graph/Dominance.expected | 39 ++++++ .../controlflow/graph/ElementGraph.expected | 22 ++++ .../controlflow/graph/EntryElement.expected | 21 +++ .../controlflow/graph/EntryPoint.expected | 1 + .../controlflow/graph/ExitElement.expected | 34 +++++ .../controlflow/graph/NodeGraph.expected | 26 ++++ .../library-tests/controlflow/graph/Switch.cs | 10 ++ .../ql/test/library-tests/csharp7/CSharp7.cs | 5 + .../csharp7/CaseCondition.expected | 3 + .../library-tests/csharp7/CaseCondition.ql | 4 + .../library-tests/csharp7/DefUse.expected | 22 ++-- .../csharp7/ExpressionBodies.expected | 2 +- .../library-tests/csharp7/ForEach.expected | 12 +- .../library-tests/csharp7/IsFlow.expected | 122 ++++++++++-------- .../library-tests/csharp7/IsPatterns.expected | 1 + .../csharp7/LocalTaintFlow.expected | 71 +++++----- .../csharp7/LocalVariables.expected | 25 ++-- .../csharp7/TaintReaches.expected | 7 +- .../csharp7/TupleAccess.expected | 8 +- .../library-tests/csharp7/TupleExpr.expected | 16 +-- .../library-tests/csharp7/TupleTypes.expected | 8 +- .../library-tests/csharp7/TypeCase1.expected | 14 +- .../library-tests/csharp7/TypeCase2.expected | 8 +- .../library-tests/csharp7/TypeCase3.expected | 2 +- 30 files changed, 419 insertions(+), 168 deletions(-) create mode 100644 csharp/ql/test/library-tests/csharp7/CaseCondition.expected create mode 100644 csharp/ql/test/library-tests/csharp7/CaseCondition.ql diff --git a/csharp/ql/src/semmle/code/csharp/Stmt.qll b/csharp/ql/src/semmle/code/csharp/Stmt.qll index 4411fc47dd41..c72dc3b135ac 100644 --- a/csharp/ql/src/semmle/code/csharp/Stmt.qll +++ b/csharp/ql/src/semmle/code/csharp/Stmt.qll @@ -250,7 +250,26 @@ class SwitchStmt extends SelectionStmt, @switch_stmt { * A `case` statement. Either a constant case (`ConstCase`), a type matching * case (`TypeCase`), or a `default` case (`DefaultCase`). */ -class CaseStmt extends Stmt, @case { } +class CaseStmt extends Stmt, @case +{ + /** + * Gets the condition on this case, if any. For example, the type case on line 3 + * has no condition, and the type case on line 4 has condition `s.Length>0`, in + * + * ``` + * switch(p) + * { + * case int i: + * case string s when s.Length>0: + * break; + * ... + * } + * ``` + */ + Expr getCondition() { + result = getChild(2) + } +} /** * A constant case of a `switch` statement, for example `case OpCode.Nop:` @@ -280,15 +299,16 @@ class ConstCase extends LabeledStmt, CaseStmt { } /** - * A type matching case in a `switch` statement, for example `case int i:` on line 2 or - * `case string s when s.Length>0:` on line 3 in + * A type matching case in a `switch` statement, for example `case int i:` on line 3 or + * `case string s when s.Length>0:` on line 4 in * * ``` - * switch(p) { - * case int i: - * case string s when s.Length>0: - * break; - * ... + * switch(p) + * { + * case int i: + * case string s when s.Length>0: + * break; + * ... * } * ``` */ @@ -350,23 +370,6 @@ class TypeCase extends LabeledStmt, CaseStmt { result = getTypeAccess().getType() } - /** - * Gets the condition on this case, if any. For example, the type case on line 2 - * has no condition, and the type case on line 3 has condition `s.Length>0`, in - * - * ``` - * switch(p) { - * case int i: - * case string s when s.Length>0: - * break; - * ... - * } - * ``` - */ - Expr getCondition() { - result = getChild(2) - } - override string toString() { exists(string var | if exists(this.getVariableDeclExpr()) then diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll index 1a4b6e81808e..0ec675d9bbe4 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll @@ -309,7 +309,7 @@ private predicate inBooleanContext(Expr e, boolean isBooleanCompletionForParent) isBooleanCompletionForParent = false ) or - exists(TypeCase tc | + exists(CaseStmt tc | tc.getCondition() = e | isBooleanCompletionForParent = false ) diff --git a/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected b/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected index bd31db44263e..3c98f6b93257 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected @@ -245,6 +245,14 @@ | Switch.cs:106:29:106:29 | 1 | Switch.cs:106:22:106:30 | return ...; | 2 | | Switch.cs:108:17:108:17 | 1 | Switch.cs:108:9:108:18 | return ...; | 3 | | Switch.cs:111:17:111:21 | enter Throw | Switch.cs:111:17:111:21 | exit Throw | 4 | +| Switch.cs:113:9:113:11 | enter M10 | Switch.cs:117:18:117:18 | 3 | 7 | +| Switch.cs:113:9:113:11 | exit M10 | Switch.cs:113:9:113:11 | exit M10 | 1 | +| Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:25:117:32 | ... == ... | 3 | +| Switch.cs:117:43:117:43 | 1 | Switch.cs:117:36:117:44 | return ...; | 2 | +| Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:18:118:18 | 2 | 2 | +| Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:25:118:31 | ... == ... | 3 | +| Switch.cs:118:42:118:42 | 2 | Switch.cs:118:35:118:43 | return ...; | 2 | +| Switch.cs:120:17:120:17 | 1 | Switch.cs:120:9:120:18 | return ...; | 3 | | TypeAccesses.cs:3:10:3:10 | enter M | TypeAccesses.cs:7:13:7:22 | ... is ... | 16 | | TypeAccesses.cs:7:25:7:25 | ; | TypeAccesses.cs:7:25:7:25 | ; | 1 | | TypeAccesses.cs:8:9:8:28 | ... ...; | TypeAccesses.cs:3:10:3:10 | exit M | 5 | diff --git a/csharp/ql/test/library-tests/controlflow/graph/BasicBlockDominance.expected b/csharp/ql/test/library-tests/controlflow/graph/BasicBlockDominance.expected index f15ca9e9c57a..be58d492bd60 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/BasicBlockDominance.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/BasicBlockDominance.expected @@ -470,6 +470,21 @@ | post | Switch.cs:106:29:106:29 | 1 | Switch.cs:106:29:106:29 | 1 | | post | Switch.cs:108:17:108:17 | 1 | Switch.cs:108:17:108:17 | 1 | | post | Switch.cs:111:17:111:21 | enter Throw | Switch.cs:111:17:111:21 | enter Throw | +| post | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:113:9:113:11 | enter M10 | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:113:9:113:11 | enter M10 | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:113:9:113:11 | exit M10 | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:117:25:117:25 | access to parameter s | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:117:43:117:43 | 1 | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:118:13:118:33 | case ...: | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:118:25:118:25 | access to parameter s | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:118:42:118:42 | 2 | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:120:17:120:17 | 1 | +| post | Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:25:117:25 | access to parameter s | +| post | Switch.cs:117:43:117:43 | 1 | Switch.cs:117:43:117:43 | 1 | +| post | Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:13:118:33 | case ...: | +| post | Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:25:118:25 | access to parameter s | +| post | Switch.cs:118:42:118:42 | 2 | Switch.cs:118:42:118:42 | 2 | +| post | Switch.cs:120:17:120:17 | 1 | Switch.cs:120:17:120:17 | 1 | | post | TypeAccesses.cs:3:10:3:10 | enter M | TypeAccesses.cs:3:10:3:10 | enter M | | post | TypeAccesses.cs:7:25:7:25 | ; | TypeAccesses.cs:7:25:7:25 | ; | | post | TypeAccesses.cs:8:9:8:28 | ... ...; | TypeAccesses.cs:3:10:3:10 | enter M | @@ -1709,6 +1724,26 @@ | pre | Switch.cs:106:29:106:29 | 1 | Switch.cs:106:29:106:29 | 1 | | pre | Switch.cs:108:17:108:17 | 1 | Switch.cs:108:17:108:17 | 1 | | pre | Switch.cs:111:17:111:21 | enter Throw | Switch.cs:111:17:111:21 | enter Throw | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:113:9:113:11 | enter M10 | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:113:9:113:11 | exit M10 | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:117:25:117:25 | access to parameter s | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:117:43:117:43 | 1 | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:118:13:118:33 | case ...: | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:118:25:118:25 | access to parameter s | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:118:42:118:42 | 2 | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:120:17:120:17 | 1 | +| pre | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:113:9:113:11 | exit M10 | +| pre | Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:25:117:25 | access to parameter s | +| pre | Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:43:117:43 | 1 | +| pre | Switch.cs:117:43:117:43 | 1 | Switch.cs:117:43:117:43 | 1 | +| pre | Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:13:118:33 | case ...: | +| pre | Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:25:118:25 | access to parameter s | +| pre | Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:42:118:42 | 2 | +| pre | Switch.cs:118:13:118:33 | case ...: | Switch.cs:120:17:120:17 | 1 | +| pre | Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:25:118:25 | access to parameter s | +| pre | Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:42:118:42 | 2 | +| pre | Switch.cs:118:42:118:42 | 2 | Switch.cs:118:42:118:42 | 2 | +| pre | Switch.cs:120:17:120:17 | 1 | Switch.cs:120:17:120:17 | 1 | | pre | TypeAccesses.cs:3:10:3:10 | enter M | TypeAccesses.cs:3:10:3:10 | enter M | | pre | TypeAccesses.cs:3:10:3:10 | enter M | TypeAccesses.cs:7:25:7:25 | ; | | pre | TypeAccesses.cs:3:10:3:10 | enter M | TypeAccesses.cs:8:9:8:28 | ... ...; | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected index 78d62f2060e8..11a8bef33d8b 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected @@ -89,6 +89,8 @@ | Switch.cs:50:30:50:38 | ... != ... | Switch.cs:51:17:51:22 | break; | true | | Switch.cs:84:19:84:23 | ... > ... | Switch.cs:85:17:85:22 | break; | true | | Switch.cs:84:19:84:23 | ... > ... | Switch.cs:86:22:86:25 | true | false | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:43:117:43 | 1 | true | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:42:118:42 | 2 | true | | TypeAccesses.cs:7:13:7:22 | ... is ... | TypeAccesses.cs:7:25:7:25 | ; | true | | VarDecls.cs:25:20:25:20 | access to parameter b | VarDecls.cs:25:24:25:24 | access to local variable x | true | | VarDecls.cs:25:20:25:20 | access to parameter b | VarDecls.cs:25:28:25:28 | access to local variable y | false | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.expected b/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.expected index 339e66061f4a..0d3b02b542f4 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionalFlow.expected @@ -117,6 +117,10 @@ | 108 | 13 | cflow.cs:108:13:108:21 | ... != ... | false | 116 | 9 | cflow.cs:116:9:116:29 | ...; | | 108 | 13 | cflow.cs:108:13:108:21 | ... != ... | true | 109 | 9 | cflow.cs:109:9:115:9 | {...} | | 110 | 20 | cflow.cs:110:20:110:23 | true | true | 111 | 13 | cflow.cs:111:13:113:13 | {...} | +| 117 | 25 | Switch.cs:117:25:117:32 | ... == ... | false | 118 | 13 | Switch.cs:118:13:118:33 | case ...: | +| 117 | 25 | Switch.cs:117:25:117:32 | ... == ... | true | 117 | 43 | Switch.cs:117:43:117:43 | 1 | +| 118 | 25 | Switch.cs:118:25:118:31 | ... == ... | false | 120 | 17 | Switch.cs:120:17:120:17 | 1 | +| 118 | 25 | Switch.cs:118:25:118:31 | ... == ... | true | 118 | 42 | Switch.cs:118:42:118:42 | 2 | | 127 | 32 | cflow.cs:127:32:127:44 | ... == ... | false | 127 | 53 | cflow.cs:127:53:127:57 | this access | | 127 | 32 | cflow.cs:127:32:127:44 | ... == ... | true | 127 | 48 | cflow.cs:127:48:127:49 | "" | | 162 | 48 | cflow.cs:162:48:162:51 | [exception: Exception] true | true | 163 | 9 | cflow.cs:163:9:165:9 | {...} | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected b/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected index 4c0403e3c388..db1629f7610a 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected @@ -819,6 +819,24 @@ | post | Switch.cs:111:17:111:21 | exit Throw | Switch.cs:111:28:111:48 | throw ... | | post | Switch.cs:111:28:111:48 | throw ... | Switch.cs:111:34:111:48 | object creation of type Exception | | post | Switch.cs:111:34:111:48 | object creation of type Exception | Switch.cs:111:17:111:21 | enter Throw | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:117:36:117:44 | return ...; | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:118:35:118:43 | return ...; | +| post | Switch.cs:113:9:113:11 | exit M10 | Switch.cs:120:9:120:18 | return ...; | +| post | Switch.cs:114:5:121:5 | {...} | Switch.cs:113:9:113:11 | enter M10 | +| post | Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:114:5:121:5 | {...} | +| post | Switch.cs:115:17:115:17 | access to parameter s | Switch.cs:115:9:119:9 | switch (...) {...} | +| post | Switch.cs:115:17:115:24 | access to property Length | Switch.cs:115:17:115:17 | access to parameter s | +| post | Switch.cs:117:13:117:34 | case ...: | Switch.cs:115:17:115:24 | access to property Length | +| post | Switch.cs:117:18:117:18 | 3 | Switch.cs:117:13:117:34 | case ...: | +| post | Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:28:117:32 | "foo" | +| post | Switch.cs:117:28:117:32 | "foo" | Switch.cs:117:25:117:25 | access to parameter s | +| post | Switch.cs:117:36:117:44 | return ...; | Switch.cs:117:43:117:43 | 1 | +| post | Switch.cs:118:18:118:18 | 2 | Switch.cs:118:13:118:33 | case ...: | +| post | Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:28:118:31 | "fu" | +| post | Switch.cs:118:28:118:31 | "fu" | Switch.cs:118:25:118:25 | access to parameter s | +| post | Switch.cs:118:35:118:43 | return ...; | Switch.cs:118:42:118:42 | 2 | +| post | Switch.cs:120:9:120:18 | return ...; | Switch.cs:120:16:120:17 | -... | +| post | Switch.cs:120:16:120:17 | -... | Switch.cs:120:17:120:17 | 1 | | post | TypeAccesses.cs:3:10:3:10 | exit M | TypeAccesses.cs:8:13:8:27 | Type t = ... | | post | TypeAccesses.cs:4:5:9:5 | {...} | TypeAccesses.cs:3:10:3:10 | enter M | | post | TypeAccesses.cs:5:9:5:26 | ... ...; | TypeAccesses.cs:4:5:9:5 | {...} | @@ -2643,6 +2661,27 @@ | pre | Switch.cs:111:17:111:21 | enter Throw | Switch.cs:111:34:111:48 | object creation of type Exception | | pre | Switch.cs:111:28:111:48 | throw ... | Switch.cs:111:17:111:21 | exit Throw | | pre | Switch.cs:111:34:111:48 | object creation of type Exception | Switch.cs:111:28:111:48 | throw ... | +| pre | Switch.cs:113:9:113:11 | enter M10 | Switch.cs:114:5:121:5 | {...} | +| pre | Switch.cs:114:5:121:5 | {...} | Switch.cs:115:9:119:9 | switch (...) {...} | +| pre | Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:115:17:115:17 | access to parameter s | +| pre | Switch.cs:115:17:115:17 | access to parameter s | Switch.cs:115:17:115:24 | access to property Length | +| pre | Switch.cs:115:17:115:24 | access to property Length | Switch.cs:117:13:117:34 | case ...: | +| pre | Switch.cs:117:13:117:34 | case ...: | Switch.cs:117:18:117:18 | 3 | +| pre | Switch.cs:117:18:117:18 | 3 | Switch.cs:117:25:117:25 | access to parameter s | +| pre | Switch.cs:117:18:117:18 | 3 | Switch.cs:118:13:118:33 | case ...: | +| pre | Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:28:117:32 | "foo" | +| pre | Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:43:117:43 | 1 | +| pre | Switch.cs:117:28:117:32 | "foo" | Switch.cs:117:25:117:32 | ... == ... | +| pre | Switch.cs:117:43:117:43 | 1 | Switch.cs:117:36:117:44 | return ...; | +| pre | Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:18:118:18 | 2 | +| pre | Switch.cs:118:18:118:18 | 2 | Switch.cs:118:25:118:25 | access to parameter s | +| pre | Switch.cs:118:18:118:18 | 2 | Switch.cs:120:17:120:17 | 1 | +| pre | Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:28:118:31 | "fu" | +| pre | Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:42:118:42 | 2 | +| pre | Switch.cs:118:28:118:31 | "fu" | Switch.cs:118:25:118:31 | ... == ... | +| pre | Switch.cs:118:42:118:42 | 2 | Switch.cs:118:35:118:43 | return ...; | +| pre | Switch.cs:120:16:120:17 | -... | Switch.cs:120:9:120:18 | return ...; | +| pre | Switch.cs:120:17:120:17 | 1 | Switch.cs:120:16:120:17 | -... | | pre | TypeAccesses.cs:3:10:3:10 | enter M | TypeAccesses.cs:4:5:9:5 | {...} | | pre | TypeAccesses.cs:4:5:9:5 | {...} | TypeAccesses.cs:5:9:5:26 | ... ...; | | pre | TypeAccesses.cs:5:9:5:26 | ... ...; | TypeAccesses.cs:5:13:5:13 | access to local variable s | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.expected b/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.expected index 0e2ccd468ed2..1c9fc895ba22 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ElementGraph.expected @@ -688,6 +688,28 @@ | Switch.cs:108:16:108:17 | -... | Switch.cs:108:9:108:18 | return ...; | semmle.label | successor | | Switch.cs:108:17:108:17 | 1 | Switch.cs:108:16:108:17 | -... | semmle.label | successor | | Switch.cs:111:34:111:48 | object creation of type Exception | Switch.cs:111:28:111:48 | throw ... | semmle.label | successor | +| Switch.cs:114:5:121:5 | {...} | Switch.cs:115:9:119:9 | switch (...) {...} | semmle.label | successor | +| Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:115:17:115:17 | access to parameter s | semmle.label | successor | +| Switch.cs:115:17:115:17 | access to parameter s | Switch.cs:115:17:115:24 | access to property Length | semmle.label | successor | +| Switch.cs:115:17:115:24 | access to property Length | Switch.cs:117:13:117:34 | case ...: | semmle.label | successor | +| Switch.cs:117:13:117:34 | case ...: | Switch.cs:117:18:117:18 | 3 | semmle.label | successor | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:25:117:25 | access to parameter s | semmle.label | match | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:118:13:118:33 | case ...: | semmle.label | no-match | +| Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:28:117:32 | "foo" | semmle.label | successor | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:43:117:43 | 1 | semmle.label | true | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:118:13:118:33 | case ...: | semmle.label | false | +| Switch.cs:117:28:117:32 | "foo" | Switch.cs:117:25:117:32 | ... == ... | semmle.label | successor | +| Switch.cs:117:43:117:43 | 1 | Switch.cs:117:36:117:44 | return ...; | semmle.label | successor | +| Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:18:118:18 | 2 | semmle.label | successor | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:25:118:25 | access to parameter s | semmle.label | match | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:120:17:120:17 | 1 | semmle.label | no-match | +| Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:28:118:31 | "fu" | semmle.label | successor | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:42:118:42 | 2 | semmle.label | true | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:120:17:120:17 | 1 | semmle.label | false | +| Switch.cs:118:28:118:31 | "fu" | Switch.cs:118:25:118:31 | ... == ... | semmle.label | successor | +| Switch.cs:118:42:118:42 | 2 | Switch.cs:118:35:118:43 | return ...; | semmle.label | successor | +| Switch.cs:120:16:120:17 | -... | Switch.cs:120:9:120:18 | return ...; | semmle.label | successor | +| Switch.cs:120:17:120:17 | 1 | Switch.cs:120:16:120:17 | -... | semmle.label | successor | | TypeAccesses.cs:4:5:9:5 | {...} | TypeAccesses.cs:5:9:5:26 | ... ...; | semmle.label | successor | | TypeAccesses.cs:5:9:5:26 | ... ...; | TypeAccesses.cs:5:13:5:13 | access to local variable s | semmle.label | successor | | TypeAccesses.cs:5:13:5:13 | access to local variable s | TypeAccesses.cs:5:25:5:25 | access to parameter o | semmle.label | successor | diff --git a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected index 5ea1d3d04a4f..7507aaf31796 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected @@ -722,6 +722,27 @@ | Switch.cs:108:17:108:17 | 1 | Switch.cs:108:17:108:17 | 1 | | Switch.cs:111:28:111:48 | throw ... | Switch.cs:111:34:111:48 | object creation of type Exception | | Switch.cs:111:34:111:48 | object creation of type Exception | Switch.cs:111:34:111:48 | object creation of type Exception | +| Switch.cs:114:5:121:5 | {...} | Switch.cs:114:5:121:5 | {...} | +| Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:115:9:119:9 | switch (...) {...} | +| Switch.cs:115:17:115:17 | access to parameter s | Switch.cs:115:17:115:17 | access to parameter s | +| Switch.cs:115:17:115:24 | access to property Length | Switch.cs:115:17:115:17 | access to parameter s | +| Switch.cs:117:13:117:34 | case ...: | Switch.cs:117:13:117:34 | case ...: | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:18:117:18 | 3 | +| Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:25:117:25 | access to parameter s | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:25:117:25 | access to parameter s | +| Switch.cs:117:28:117:32 | "foo" | Switch.cs:117:28:117:32 | "foo" | +| Switch.cs:117:36:117:44 | return ...; | Switch.cs:117:43:117:43 | 1 | +| Switch.cs:117:43:117:43 | 1 | Switch.cs:117:43:117:43 | 1 | +| Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:13:118:33 | case ...: | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:18:118:18 | 2 | +| Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:25:118:25 | access to parameter s | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:25:118:25 | access to parameter s | +| Switch.cs:118:28:118:31 | "fu" | Switch.cs:118:28:118:31 | "fu" | +| Switch.cs:118:35:118:43 | return ...; | Switch.cs:118:42:118:42 | 2 | +| Switch.cs:118:42:118:42 | 2 | Switch.cs:118:42:118:42 | 2 | +| Switch.cs:120:9:120:18 | return ...; | Switch.cs:120:17:120:17 | 1 | +| Switch.cs:120:16:120:17 | -... | Switch.cs:120:17:120:17 | 1 | +| Switch.cs:120:17:120:17 | 1 | Switch.cs:120:17:120:17 | 1 | | TypeAccesses.cs:4:5:9:5 | {...} | TypeAccesses.cs:4:5:9:5 | {...} | | TypeAccesses.cs:5:9:5:26 | ... ...; | TypeAccesses.cs:5:9:5:26 | ... ...; | | TypeAccesses.cs:5:13:5:13 | access to local variable s | TypeAccesses.cs:5:13:5:13 | access to local variable s | diff --git a/csharp/ql/test/library-tests/controlflow/graph/EntryPoint.expected b/csharp/ql/test/library-tests/controlflow/graph/EntryPoint.expected index 91442da7eafd..b0983bb86a3e 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EntryPoint.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/EntryPoint.expected @@ -65,6 +65,7 @@ | Switch.cs:91:10:91:11 | M8 | Switch.cs:92:5:99:5 | {...} | | Switch.cs:101:9:101:10 | M9 | Switch.cs:102:5:109:5 | {...} | | Switch.cs:111:17:111:21 | Throw | Switch.cs:111:34:111:48 | object creation of type Exception | +| Switch.cs:113:9:113:11 | M10 | Switch.cs:114:5:121:5 | {...} | | TypeAccesses.cs:3:10:3:10 | M | TypeAccesses.cs:4:5:9:5 | {...} | | VarDecls.cs:5:18:5:19 | M1 | VarDecls.cs:6:5:11:5 | {...} | | VarDecls.cs:13:12:13:13 | M2 | VarDecls.cs:14:5:17:5 | {...} | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected index 8b33624c773e..d7632183e930 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected @@ -1020,6 +1020,40 @@ | Switch.cs:108:17:108:17 | 1 | Switch.cs:108:17:108:17 | 1 | normal | | Switch.cs:111:28:111:48 | throw ... | Switch.cs:111:28:111:48 | throw ... | throw(Exception) | | Switch.cs:111:34:111:48 | object creation of type Exception | Switch.cs:111:34:111:48 | object creation of type Exception | normal | +| Switch.cs:114:5:121:5 | {...} | Switch.cs:117:36:117:44 | return ...; | return | +| Switch.cs:114:5:121:5 | {...} | Switch.cs:118:35:118:43 | return ...; | return | +| Switch.cs:114:5:121:5 | {...} | Switch.cs:120:9:120:18 | return ...; | return | +| Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:117:36:117:44 | return ...; | return | +| Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:118:18:118:18 | 2 | no-match | +| Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:118:25:118:31 | ... == ... | false/false | +| Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:118:35:118:43 | return ...; | return | +| Switch.cs:115:17:115:17 | access to parameter s | Switch.cs:115:17:115:17 | access to parameter s | normal | +| Switch.cs:115:17:115:24 | access to property Length | Switch.cs:115:17:115:24 | access to property Length | normal | +| Switch.cs:117:13:117:34 | case ...: | Switch.cs:117:18:117:18 | 3 | no-match | +| Switch.cs:117:13:117:34 | case ...: | Switch.cs:117:25:117:32 | ... == ... | false/false | +| Switch.cs:117:13:117:34 | case ...: | Switch.cs:117:36:117:44 | return ...; | return | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:18:117:18 | 3 | match | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:18:117:18 | 3 | no-match | +| Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:25:117:25 | access to parameter s | normal | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:25:117:32 | ... == ... | false/false | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:25:117:32 | ... == ... | true/true | +| Switch.cs:117:28:117:32 | "foo" | Switch.cs:117:28:117:32 | "foo" | normal | +| Switch.cs:117:36:117:44 | return ...; | Switch.cs:117:36:117:44 | return ...; | return | +| Switch.cs:117:43:117:43 | 1 | Switch.cs:117:43:117:43 | 1 | normal | +| Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:18:118:18 | 2 | no-match | +| Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:25:118:31 | ... == ... | false/false | +| Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:35:118:43 | return ...; | return | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:18:118:18 | 2 | match | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:18:118:18 | 2 | no-match | +| Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:25:118:25 | access to parameter s | normal | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:25:118:31 | ... == ... | false/false | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:25:118:31 | ... == ... | true/true | +| Switch.cs:118:28:118:31 | "fu" | Switch.cs:118:28:118:31 | "fu" | normal | +| Switch.cs:118:35:118:43 | return ...; | Switch.cs:118:35:118:43 | return ...; | return | +| Switch.cs:118:42:118:42 | 2 | Switch.cs:118:42:118:42 | 2 | normal | +| Switch.cs:120:9:120:18 | return ...; | Switch.cs:120:9:120:18 | return ...; | return | +| Switch.cs:120:16:120:17 | -... | Switch.cs:120:16:120:17 | -... | normal | +| Switch.cs:120:17:120:17 | 1 | Switch.cs:120:17:120:17 | 1 | normal | | TypeAccesses.cs:4:5:9:5 | {...} | TypeAccesses.cs:8:13:8:27 | Type t = ... | normal | | TypeAccesses.cs:5:9:5:26 | ... ...; | TypeAccesses.cs:5:13:5:25 | String s = ... | normal | | TypeAccesses.cs:5:13:5:13 | access to local variable s | TypeAccesses.cs:5:13:5:13 | access to local variable s | normal | diff --git a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected index d8420a545315..ccb9d52180a7 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected @@ -976,6 +976,32 @@ | Switch.cs:111:17:111:21 | enter Throw | Switch.cs:111:34:111:48 | object creation of type Exception | semmle.label | successor | | Switch.cs:111:28:111:48 | throw ... | Switch.cs:111:17:111:21 | exit Throw | semmle.label | exception(Exception) | | Switch.cs:111:34:111:48 | object creation of type Exception | Switch.cs:111:28:111:48 | throw ... | semmle.label | successor | +| Switch.cs:113:9:113:11 | enter M10 | Switch.cs:114:5:121:5 | {...} | semmle.label | successor | +| Switch.cs:114:5:121:5 | {...} | Switch.cs:115:9:119:9 | switch (...) {...} | semmle.label | successor | +| Switch.cs:115:9:119:9 | switch (...) {...} | Switch.cs:115:17:115:17 | access to parameter s | semmle.label | successor | +| Switch.cs:115:17:115:17 | access to parameter s | Switch.cs:115:17:115:24 | access to property Length | semmle.label | successor | +| Switch.cs:115:17:115:24 | access to property Length | Switch.cs:117:13:117:34 | case ...: | semmle.label | successor | +| Switch.cs:117:13:117:34 | case ...: | Switch.cs:117:18:117:18 | 3 | semmle.label | successor | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:25:117:25 | access to parameter s | semmle.label | match | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:118:13:118:33 | case ...: | semmle.label | no-match | +| Switch.cs:117:25:117:25 | access to parameter s | Switch.cs:117:28:117:32 | "foo" | semmle.label | successor | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:43:117:43 | 1 | semmle.label | true | +| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:118:13:118:33 | case ...: | semmle.label | false | +| Switch.cs:117:28:117:32 | "foo" | Switch.cs:117:25:117:32 | ... == ... | semmle.label | successor | +| Switch.cs:117:36:117:44 | return ...; | Switch.cs:113:9:113:11 | exit M10 | semmle.label | return | +| Switch.cs:117:43:117:43 | 1 | Switch.cs:117:36:117:44 | return ...; | semmle.label | successor | +| Switch.cs:118:13:118:33 | case ...: | Switch.cs:118:18:118:18 | 2 | semmle.label | successor | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:25:118:25 | access to parameter s | semmle.label | match | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:120:17:120:17 | 1 | semmle.label | no-match | +| Switch.cs:118:25:118:25 | access to parameter s | Switch.cs:118:28:118:31 | "fu" | semmle.label | successor | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:42:118:42 | 2 | semmle.label | true | +| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:120:17:120:17 | 1 | semmle.label | false | +| Switch.cs:118:28:118:31 | "fu" | Switch.cs:118:25:118:31 | ... == ... | semmle.label | successor | +| Switch.cs:118:35:118:43 | return ...; | Switch.cs:113:9:113:11 | exit M10 | semmle.label | return | +| Switch.cs:118:42:118:42 | 2 | Switch.cs:118:35:118:43 | return ...; | semmle.label | successor | +| Switch.cs:120:9:120:18 | return ...; | Switch.cs:113:9:113:11 | exit M10 | semmle.label | return | +| Switch.cs:120:16:120:17 | -... | Switch.cs:120:9:120:18 | return ...; | semmle.label | successor | +| Switch.cs:120:17:120:17 | 1 | Switch.cs:120:16:120:17 | -... | semmle.label | successor | | TypeAccesses.cs:3:10:3:10 | enter M | TypeAccesses.cs:4:5:9:5 | {...} | semmle.label | successor | | TypeAccesses.cs:4:5:9:5 | {...} | TypeAccesses.cs:5:9:5:26 | ... ...; | semmle.label | successor | | TypeAccesses.cs:5:9:5:26 | ... ...; | TypeAccesses.cs:5:13:5:13 | access to local variable s | semmle.label | successor | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Switch.cs b/csharp/ql/test/library-tests/controlflow/graph/Switch.cs index 5beda3cee4d4..50b1470974dc 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Switch.cs +++ b/csharp/ql/test/library-tests/controlflow/graph/Switch.cs @@ -109,4 +109,14 @@ int M9(string s) } static bool Throw() => throw new Exception(); + + int M10(string s) + { + switch (s.Length) + { + case 3 when s=="foo" : return 1; + case 2 when s=="fu" : return 2; + } + return -1; + } } diff --git a/csharp/ql/test/library-tests/csharp7/CSharp7.cs b/csharp/ql/test/library-tests/csharp7/CSharp7.cs index 3399236de038..7a2898a97e46 100644 --- a/csharp/ql/test/library-tests/csharp7/CSharp7.cs +++ b/csharp/ql/test/library-tests/csharp7/CSharp7.cs @@ -250,6 +250,11 @@ void Test() { case "xyz": break; + case "" when 1 < 2: + break; + case "x" when o is string s4: + Console.WriteLine($"x {s4}"); + break; case int i2 when i2 > 0: Console.WriteLine($"positive {i2}"); break; diff --git a/csharp/ql/test/library-tests/csharp7/CaseCondition.expected b/csharp/ql/test/library-tests/csharp7/CaseCondition.expected new file mode 100644 index 000000000000..4f3906c7646e --- /dev/null +++ b/csharp/ql/test/library-tests/csharp7/CaseCondition.expected @@ -0,0 +1,3 @@ +| CSharp7.cs:253:13:253:31 | case ...: | CSharp7.cs:253:26:253:30 | ... < ... | +| CSharp7.cs:255:13:255:41 | case ...: | CSharp7.cs:255:27:255:40 | ... is ... | +| CSharp7.cs:258:13:258:36 | case Int32 i2: | CSharp7.cs:258:30:258:35 | ... > ... | diff --git a/csharp/ql/test/library-tests/csharp7/CaseCondition.ql b/csharp/ql/test/library-tests/csharp7/CaseCondition.ql new file mode 100644 index 000000000000..f3001e205464 --- /dev/null +++ b/csharp/ql/test/library-tests/csharp7/CaseCondition.ql @@ -0,0 +1,4 @@ +import csharp + +from CaseStmt stmt +select stmt, stmt.getCondition() diff --git a/csharp/ql/test/library-tests/csharp7/DefUse.expected b/csharp/ql/test/library-tests/csharp7/DefUse.expected index 84dd481a11de..e6f5a6c407b9 100644 --- a/csharp/ql/test/library-tests/csharp7/DefUse.expected +++ b/csharp/ql/test/library-tests/csharp7/DefUse.expected @@ -51,16 +51,18 @@ | CSharp7.cs:233:16:233:23 | Object o = ... | CSharp7.cs:242:18:242:18 | access to local variable o | | CSharp7.cs:233:16:233:23 | Object o = ... | CSharp7.cs:245:18:245:18 | access to local variable o | | CSharp7.cs:233:16:233:23 | Object o = ... | CSharp7.cs:249:17:249:17 | access to local variable o | +| CSharp7.cs:233:16:233:23 | Object o = ... | CSharp7.cs:255:27:255:27 | access to local variable o | | CSharp7.cs:234:18:234:23 | Int32 i1 | CSharp7.cs:234:28:234:29 | access to local variable i1 | | CSharp7.cs:234:18:234:23 | Int32 i1 | CSharp7.cs:236:38:236:39 | access to local variable i1 | | CSharp7.cs:238:23:238:31 | String s1 | CSharp7.cs:240:41:240:42 | access to local variable s1 | -| CSharp7.cs:253:18:253:23 | Int32 i2 | CSharp7.cs:253:30:253:31 | access to local variable i2 | -| CSharp7.cs:253:18:253:23 | Int32 i2 | CSharp7.cs:254:47:254:48 | access to local variable i2 | -| CSharp7.cs:256:18:256:23 | Int32 i3 | CSharp7.cs:257:42:257:43 | access to local variable i3 | -| CSharp7.cs:259:18:259:26 | String s2 | CSharp7.cs:260:45:260:46 | access to local variable s2 | -| CSharp7.cs:278:13:278:48 | Dictionary dict = ... | CSharp7.cs:279:20:279:23 | access to local variable dict | -| CSharp7.cs:279:13:279:62 | IEnumerable<(Int32,String)> list = ... | CSharp7.cs:281:39:281:42 | access to local variable list | -| CSharp7.cs:279:13:279:62 | IEnumerable<(Int32,String)> list = ... | CSharp7.cs:283:36:283:39 | access to local variable list | -| CSharp7.cs:279:13:279:62 | IEnumerable<(Int32,String)> list = ... | CSharp7.cs:285:32:285:35 | access to local variable list | -| CSharp7.cs:279:32:279:35 | item | CSharp7.cs:279:41:279:44 | access to parameter item | -| CSharp7.cs:279:32:279:35 | item | CSharp7.cs:279:51:279:54 | access to parameter item | +| CSharp7.cs:255:32:255:40 | String s4 | CSharp7.cs:256:40:256:41 | access to local variable s4 | +| CSharp7.cs:258:18:258:23 | Int32 i2 | CSharp7.cs:258:30:258:31 | access to local variable i2 | +| CSharp7.cs:258:18:258:23 | Int32 i2 | CSharp7.cs:259:47:259:48 | access to local variable i2 | +| CSharp7.cs:261:18:261:23 | Int32 i3 | CSharp7.cs:262:42:262:43 | access to local variable i3 | +| CSharp7.cs:264:18:264:26 | String s2 | CSharp7.cs:265:45:265:46 | access to local variable s2 | +| CSharp7.cs:283:13:283:48 | Dictionary dict = ... | CSharp7.cs:284:20:284:23 | access to local variable dict | +| CSharp7.cs:284:13:284:62 | IEnumerable<(Int32,String)> list = ... | CSharp7.cs:286:39:286:42 | access to local variable list | +| CSharp7.cs:284:13:284:62 | IEnumerable<(Int32,String)> list = ... | CSharp7.cs:288:36:288:39 | access to local variable list | +| CSharp7.cs:284:13:284:62 | IEnumerable<(Int32,String)> list = ... | CSharp7.cs:290:32:290:35 | access to local variable list | +| CSharp7.cs:284:32:284:35 | item | CSharp7.cs:284:41:284:44 | access to parameter item | +| CSharp7.cs:284:32:284:35 | item | CSharp7.cs:284:51:284:54 | access to parameter item | diff --git a/csharp/ql/test/library-tests/csharp7/ExpressionBodies.expected b/csharp/ql/test/library-tests/csharp7/ExpressionBodies.expected index fac870c15776..666074d0a7b8 100644 --- a/csharp/ql/test/library-tests/csharp7/ExpressionBodies.expected +++ b/csharp/ql/test/library-tests/csharp7/ExpressionBodies.expected @@ -15,4 +15,4 @@ | CSharp7.cs:165:13:165:31 | f2 | CSharp7.cs:165:25:165:30 | call to local function f | | CSharp7.cs:177:9:177:40 | f | CSharp7.cs:177:31:177:39 | ... + ... | | CSharp7.cs:178:9:178:32 | g | CSharp7.cs:178:31:178:31 | access to parameter s | -| CSharp7.cs:279:32:279:61 | (...) => ... | CSharp7.cs:279:40:279:61 | (..., ...) | +| CSharp7.cs:284:32:284:61 | (...) => ... | CSharp7.cs:284:40:284:61 | (..., ...) | diff --git a/csharp/ql/test/library-tests/csharp7/ForEach.expected b/csharp/ql/test/library-tests/csharp7/ForEach.expected index c6e46c2a4e77..71f3d7cb0ae1 100644 --- a/csharp/ql/test/library-tests/csharp7/ForEach.expected +++ b/csharp/ql/test/library-tests/csharp7/ForEach.expected @@ -1,6 +1,6 @@ -| CSharp7.cs:281:9:281:47 | foreach (... ... in ...) ... | 0 | CSharp7.cs:281:23:281:23 | Int32 a | CSharp7.cs:281:23:281:23 | a | CSharp7.cs:281:39:281:42 | access to local variable list | CSharp7.cs:281:45:281:47 | {...} | -| CSharp7.cs:281:9:281:47 | foreach (... ... in ...) ... | 1 | CSharp7.cs:281:33:281:33 | String b | CSharp7.cs:281:33:281:33 | b | CSharp7.cs:281:39:281:42 | access to local variable list | CSharp7.cs:281:45:281:47 | {...} | -| CSharp7.cs:283:9:283:44 | foreach (... ... in ...) ... | 0 | CSharp7.cs:283:23:283:23 | Int32 a | CSharp7.cs:283:23:283:23 | a | CSharp7.cs:283:36:283:39 | access to local variable list | CSharp7.cs:283:42:283:44 | {...} | -| CSharp7.cs:283:9:283:44 | foreach (... ... in ...) ... | 1 | CSharp7.cs:283:30:283:30 | String b | CSharp7.cs:283:30:283:30 | b | CSharp7.cs:283:36:283:39 | access to local variable list | CSharp7.cs:283:42:283:44 | {...} | -| CSharp7.cs:285:9:285:40 | foreach (... ... in ...) ... | 0 | CSharp7.cs:285:23:285:23 | Int32 a | CSharp7.cs:285:23:285:23 | a | CSharp7.cs:285:32:285:35 | access to local variable list | CSharp7.cs:285:38:285:40 | {...} | -| CSharp7.cs:285:9:285:40 | foreach (... ... in ...) ... | 1 | CSharp7.cs:285:26:285:26 | String b | CSharp7.cs:285:26:285:26 | b | CSharp7.cs:285:32:285:35 | access to local variable list | CSharp7.cs:285:38:285:40 | {...} | +| CSharp7.cs:286:9:286:47 | foreach (... ... in ...) ... | 0 | CSharp7.cs:286:23:286:23 | Int32 a | CSharp7.cs:286:23:286:23 | a | CSharp7.cs:286:39:286:42 | access to local variable list | CSharp7.cs:286:45:286:47 | {...} | +| CSharp7.cs:286:9:286:47 | foreach (... ... in ...) ... | 1 | CSharp7.cs:286:33:286:33 | String b | CSharp7.cs:286:33:286:33 | b | CSharp7.cs:286:39:286:42 | access to local variable list | CSharp7.cs:286:45:286:47 | {...} | +| CSharp7.cs:288:9:288:44 | foreach (... ... in ...) ... | 0 | CSharp7.cs:288:23:288:23 | Int32 a | CSharp7.cs:288:23:288:23 | a | CSharp7.cs:288:36:288:39 | access to local variable list | CSharp7.cs:288:42:288:44 | {...} | +| CSharp7.cs:288:9:288:44 | foreach (... ... in ...) ... | 1 | CSharp7.cs:288:30:288:30 | String b | CSharp7.cs:288:30:288:30 | b | CSharp7.cs:288:36:288:39 | access to local variable list | CSharp7.cs:288:42:288:44 | {...} | +| CSharp7.cs:290:9:290:40 | foreach (... ... in ...) ... | 0 | CSharp7.cs:290:23:290:23 | Int32 a | CSharp7.cs:290:23:290:23 | a | CSharp7.cs:290:32:290:35 | access to local variable list | CSharp7.cs:290:38:290:40 | {...} | +| CSharp7.cs:290:9:290:40 | foreach (... ... in ...) ... | 1 | CSharp7.cs:290:26:290:26 | String b | CSharp7.cs:290:26:290:26 | b | CSharp7.cs:290:32:290:35 | access to local variable list | CSharp7.cs:290:38:290:40 | {...} | diff --git a/csharp/ql/test/library-tests/csharp7/IsFlow.expected b/csharp/ql/test/library-tests/csharp7/IsFlow.expected index 77b74fb5e28f..f22e35b0bf2e 100644 --- a/csharp/ql/test/library-tests/csharp7/IsFlow.expected +++ b/csharp/ql/test/library-tests/csharp7/IsFlow.expected @@ -1,57 +1,77 @@ -| CSharp7.cs:249:9:270:9 | switch (...) {...} | CSharp7.cs:249:17:249:17 | access to local variable o | semmle.label | successor | +| CSharp7.cs:249:9:275:9 | switch (...) {...} | CSharp7.cs:249:17:249:17 | access to local variable o | semmle.label | successor | | CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:251:13:251:23 | case ...: | semmle.label | successor | | CSharp7.cs:251:13:251:23 | case ...: | CSharp7.cs:251:18:251:22 | "xyz" | semmle.label | successor | | CSharp7.cs:251:18:251:22 | "xyz" | CSharp7.cs:252:17:252:22 | break; | semmle.label | match | -| CSharp7.cs:251:18:251:22 | "xyz" | CSharp7.cs:253:13:253:36 | case Int32 i2: | semmle.label | no-match | +| CSharp7.cs:251:18:251:22 | "xyz" | CSharp7.cs:253:13:253:31 | case ...: | semmle.label | no-match | | CSharp7.cs:252:17:252:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | -| CSharp7.cs:253:13:253:36 | case Int32 i2: | CSharp7.cs:253:18:253:20 | access to type Int32 | semmle.label | successor | -| CSharp7.cs:253:18:253:20 | access to type Int32 | CSharp7.cs:253:18:253:23 | Int32 i2 | semmle.label | match | -| CSharp7.cs:253:18:253:20 | access to type Int32 | CSharp7.cs:256:13:256:24 | case Int32 i3: | semmle.label | no-match | -| CSharp7.cs:253:18:253:23 | Int32 i2 | CSharp7.cs:253:30:253:31 | access to local variable i2 | semmle.label | successor | -| CSharp7.cs:253:30:253:31 | access to local variable i2 | CSharp7.cs:253:35:253:35 | 0 | semmle.label | successor | -| CSharp7.cs:253:30:253:35 | ... > ... | CSharp7.cs:254:17:254:52 | ...; | semmle.label | true | -| CSharp7.cs:253:30:253:35 | ... > ... | CSharp7.cs:256:13:256:24 | case Int32 i3: | semmle.label | false | -| CSharp7.cs:253:35:253:35 | 0 | CSharp7.cs:253:30:253:35 | ... > ... | semmle.label | successor | -| CSharp7.cs:254:17:254:51 | call to method WriteLine | CSharp7.cs:255:17:255:22 | break; | semmle.label | successor | -| CSharp7.cs:254:17:254:52 | ...; | CSharp7.cs:254:37:254:45 | "positive " | semmle.label | successor | -| CSharp7.cs:254:35:254:50 | $"..." | CSharp7.cs:254:17:254:51 | call to method WriteLine | semmle.label | successor | -| CSharp7.cs:254:37:254:45 | "positive " | CSharp7.cs:254:47:254:48 | access to local variable i2 | semmle.label | successor | -| CSharp7.cs:254:47:254:48 | access to local variable i2 | CSharp7.cs:254:35:254:50 | $"..." | semmle.label | successor | -| CSharp7.cs:255:17:255:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | -| CSharp7.cs:256:13:256:24 | case Int32 i3: | CSharp7.cs:256:18:256:20 | access to type Int32 | semmle.label | successor | -| CSharp7.cs:256:18:256:20 | access to type Int32 | CSharp7.cs:256:18:256:23 | Int32 i3 | semmle.label | match | -| CSharp7.cs:256:18:256:20 | access to type Int32 | CSharp7.cs:259:13:259:27 | case String s2: | semmle.label | no-match | -| CSharp7.cs:256:18:256:23 | Int32 i3 | CSharp7.cs:257:17:257:47 | ...; | semmle.label | successor | -| CSharp7.cs:257:17:257:46 | call to method WriteLine | CSharp7.cs:258:17:258:22 | break; | semmle.label | successor | -| CSharp7.cs:257:17:257:47 | ...; | CSharp7.cs:257:37:257:40 | "int " | semmle.label | successor | -| CSharp7.cs:257:35:257:45 | $"..." | CSharp7.cs:257:17:257:46 | call to method WriteLine | semmle.label | successor | -| CSharp7.cs:257:37:257:40 | "int " | CSharp7.cs:257:42:257:43 | access to local variable i3 | semmle.label | successor | -| CSharp7.cs:257:42:257:43 | access to local variable i3 | CSharp7.cs:257:35:257:45 | $"..." | semmle.label | successor | -| CSharp7.cs:258:17:258:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | -| CSharp7.cs:259:13:259:27 | case String s2: | CSharp7.cs:259:18:259:23 | access to type String | semmle.label | successor | -| CSharp7.cs:259:18:259:23 | access to type String | CSharp7.cs:259:18:259:26 | String s2 | semmle.label | match | -| CSharp7.cs:259:18:259:23 | access to type String | CSharp7.cs:262:13:262:26 | case Double: | semmle.label | no-match | -| CSharp7.cs:259:18:259:26 | String s2 | CSharp7.cs:260:17:260:50 | ...; | semmle.label | successor | -| CSharp7.cs:260:17:260:49 | call to method WriteLine | CSharp7.cs:261:17:261:22 | break; | semmle.label | successor | -| CSharp7.cs:260:17:260:50 | ...; | CSharp7.cs:260:37:260:43 | "string " | semmle.label | successor | -| CSharp7.cs:260:35:260:48 | $"..." | CSharp7.cs:260:17:260:49 | call to method WriteLine | semmle.label | successor | -| CSharp7.cs:260:37:260:43 | "string " | CSharp7.cs:260:45:260:46 | access to local variable s2 | semmle.label | successor | -| CSharp7.cs:260:45:260:46 | access to local variable s2 | CSharp7.cs:260:35:260:48 | $"..." | semmle.label | successor | -| CSharp7.cs:261:17:261:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | -| CSharp7.cs:262:13:262:26 | case Double: | CSharp7.cs:262:18:262:23 | access to type Double | semmle.label | successor | -| CSharp7.cs:262:18:262:23 | access to type Double | CSharp7.cs:263:17:263:44 | ...; | semmle.label | match | -| CSharp7.cs:262:18:262:23 | access to type Double | CSharp7.cs:265:13:265:24 | case Object v2: | semmle.label | no-match | -| CSharp7.cs:263:17:263:43 | call to method WriteLine | CSharp7.cs:264:17:264:22 | break; | semmle.label | successor | -| CSharp7.cs:263:17:263:44 | ...; | CSharp7.cs:263:35:263:42 | "Double" | semmle.label | successor | -| CSharp7.cs:263:35:263:42 | "Double" | CSharp7.cs:263:17:263:43 | call to method WriteLine | semmle.label | successor | -| CSharp7.cs:264:17:264:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | -| CSharp7.cs:265:13:265:24 | case Object v2: | CSharp7.cs:265:18:265:20 | access to type Object | semmle.label | successor | -| CSharp7.cs:265:18:265:20 | access to type Object | CSharp7.cs:265:18:265:23 | Object v2 | semmle.label | match | -| CSharp7.cs:265:18:265:20 | access to type Object | CSharp7.cs:267:13:267:20 | default: | semmle.label | no-match | -| CSharp7.cs:265:18:265:23 | Object v2 | CSharp7.cs:266:17:266:22 | break; | semmle.label | successor | +| CSharp7.cs:253:13:253:31 | case ...: | CSharp7.cs:253:18:253:19 | "" | semmle.label | successor | +| CSharp7.cs:253:18:253:19 | "" | CSharp7.cs:253:26:253:26 | 1 | semmle.label | match | +| CSharp7.cs:253:18:253:19 | "" | CSharp7.cs:255:13:255:41 | case ...: | semmle.label | no-match | +| CSharp7.cs:253:26:253:26 | 1 | CSharp7.cs:253:30:253:30 | 2 | semmle.label | successor | +| CSharp7.cs:253:26:253:30 | ... < ... | CSharp7.cs:254:17:254:22 | break; | semmle.label | true | +| CSharp7.cs:253:30:253:30 | 2 | CSharp7.cs:253:26:253:30 | ... < ... | semmle.label | successor | +| CSharp7.cs:254:17:254:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | +| CSharp7.cs:255:13:255:41 | case ...: | CSharp7.cs:255:18:255:20 | "x" | semmle.label | successor | +| CSharp7.cs:255:18:255:20 | "x" | CSharp7.cs:255:27:255:27 | access to local variable o | semmle.label | match | +| CSharp7.cs:255:18:255:20 | "x" | CSharp7.cs:258:13:258:36 | case Int32 i2: | semmle.label | no-match | +| CSharp7.cs:255:27:255:27 | access to local variable o | CSharp7.cs:255:32:255:40 | String s4 | semmle.label | successor | +| CSharp7.cs:255:27:255:40 | ... is ... | CSharp7.cs:256:17:256:45 | ...; | semmle.label | true | +| CSharp7.cs:255:27:255:40 | ... is ... | CSharp7.cs:258:13:258:36 | case Int32 i2: | semmle.label | false | +| CSharp7.cs:255:32:255:40 | String s4 | CSharp7.cs:255:27:255:40 | ... is ... | semmle.label | successor | +| CSharp7.cs:256:17:256:44 | call to method WriteLine | CSharp7.cs:257:17:257:22 | break; | semmle.label | successor | +| CSharp7.cs:256:17:256:45 | ...; | CSharp7.cs:256:37:256:38 | "x " | semmle.label | successor | +| CSharp7.cs:256:35:256:43 | $"..." | CSharp7.cs:256:17:256:44 | call to method WriteLine | semmle.label | successor | +| CSharp7.cs:256:37:256:38 | "x " | CSharp7.cs:256:40:256:41 | access to local variable s4 | semmle.label | successor | +| CSharp7.cs:256:40:256:41 | access to local variable s4 | CSharp7.cs:256:35:256:43 | $"..." | semmle.label | successor | +| CSharp7.cs:257:17:257:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | +| CSharp7.cs:258:13:258:36 | case Int32 i2: | CSharp7.cs:258:18:258:20 | access to type Int32 | semmle.label | successor | +| CSharp7.cs:258:18:258:20 | access to type Int32 | CSharp7.cs:258:18:258:23 | Int32 i2 | semmle.label | match | +| CSharp7.cs:258:18:258:20 | access to type Int32 | CSharp7.cs:261:13:261:24 | case Int32 i3: | semmle.label | no-match | +| CSharp7.cs:258:18:258:23 | Int32 i2 | CSharp7.cs:258:30:258:31 | access to local variable i2 | semmle.label | successor | +| CSharp7.cs:258:30:258:31 | access to local variable i2 | CSharp7.cs:258:35:258:35 | 0 | semmle.label | successor | +| CSharp7.cs:258:30:258:35 | ... > ... | CSharp7.cs:259:17:259:52 | ...; | semmle.label | true | +| CSharp7.cs:258:30:258:35 | ... > ... | CSharp7.cs:261:13:261:24 | case Int32 i3: | semmle.label | false | +| CSharp7.cs:258:35:258:35 | 0 | CSharp7.cs:258:30:258:35 | ... > ... | semmle.label | successor | +| CSharp7.cs:259:17:259:51 | call to method WriteLine | CSharp7.cs:260:17:260:22 | break; | semmle.label | successor | +| CSharp7.cs:259:17:259:52 | ...; | CSharp7.cs:259:37:259:45 | "positive " | semmle.label | successor | +| CSharp7.cs:259:35:259:50 | $"..." | CSharp7.cs:259:17:259:51 | call to method WriteLine | semmle.label | successor | +| CSharp7.cs:259:37:259:45 | "positive " | CSharp7.cs:259:47:259:48 | access to local variable i2 | semmle.label | successor | +| CSharp7.cs:259:47:259:48 | access to local variable i2 | CSharp7.cs:259:35:259:50 | $"..." | semmle.label | successor | +| CSharp7.cs:260:17:260:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | +| CSharp7.cs:261:13:261:24 | case Int32 i3: | CSharp7.cs:261:18:261:20 | access to type Int32 | semmle.label | successor | +| CSharp7.cs:261:18:261:20 | access to type Int32 | CSharp7.cs:261:18:261:23 | Int32 i3 | semmle.label | match | +| CSharp7.cs:261:18:261:20 | access to type Int32 | CSharp7.cs:264:13:264:27 | case String s2: | semmle.label | no-match | +| CSharp7.cs:261:18:261:23 | Int32 i3 | CSharp7.cs:262:17:262:47 | ...; | semmle.label | successor | +| CSharp7.cs:262:17:262:46 | call to method WriteLine | CSharp7.cs:263:17:263:22 | break; | semmle.label | successor | +| CSharp7.cs:262:17:262:47 | ...; | CSharp7.cs:262:37:262:40 | "int " | semmle.label | successor | +| CSharp7.cs:262:35:262:45 | $"..." | CSharp7.cs:262:17:262:46 | call to method WriteLine | semmle.label | successor | +| CSharp7.cs:262:37:262:40 | "int " | CSharp7.cs:262:42:262:43 | access to local variable i3 | semmle.label | successor | +| CSharp7.cs:262:42:262:43 | access to local variable i3 | CSharp7.cs:262:35:262:45 | $"..." | semmle.label | successor | +| CSharp7.cs:263:17:263:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | +| CSharp7.cs:264:13:264:27 | case String s2: | CSharp7.cs:264:18:264:23 | access to type String | semmle.label | successor | +| CSharp7.cs:264:18:264:23 | access to type String | CSharp7.cs:264:18:264:26 | String s2 | semmle.label | match | +| CSharp7.cs:264:18:264:23 | access to type String | CSharp7.cs:267:13:267:26 | case Double: | semmle.label | no-match | +| CSharp7.cs:264:18:264:26 | String s2 | CSharp7.cs:265:17:265:50 | ...; | semmle.label | successor | +| CSharp7.cs:265:17:265:49 | call to method WriteLine | CSharp7.cs:266:17:266:22 | break; | semmle.label | successor | +| CSharp7.cs:265:17:265:50 | ...; | CSharp7.cs:265:37:265:43 | "string " | semmle.label | successor | +| CSharp7.cs:265:35:265:48 | $"..." | CSharp7.cs:265:17:265:49 | call to method WriteLine | semmle.label | successor | +| CSharp7.cs:265:37:265:43 | "string " | CSharp7.cs:265:45:265:46 | access to local variable s2 | semmle.label | successor | +| CSharp7.cs:265:45:265:46 | access to local variable s2 | CSharp7.cs:265:35:265:48 | $"..." | semmle.label | successor | | CSharp7.cs:266:17:266:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | -| CSharp7.cs:267:13:267:20 | default: | CSharp7.cs:268:17:268:52 | ...; | semmle.label | successor | -| CSharp7.cs:268:17:268:51 | call to method WriteLine | CSharp7.cs:269:17:269:22 | break; | semmle.label | successor | -| CSharp7.cs:268:17:268:52 | ...; | CSharp7.cs:268:35:268:50 | "Something else" | semmle.label | successor | -| CSharp7.cs:268:35:268:50 | "Something else" | CSharp7.cs:268:17:268:51 | call to method WriteLine | semmle.label | successor | +| CSharp7.cs:267:13:267:26 | case Double: | CSharp7.cs:267:18:267:23 | access to type Double | semmle.label | successor | +| CSharp7.cs:267:18:267:23 | access to type Double | CSharp7.cs:268:17:268:44 | ...; | semmle.label | match | +| CSharp7.cs:267:18:267:23 | access to type Double | CSharp7.cs:270:13:270:24 | case Object v2: | semmle.label | no-match | +| CSharp7.cs:268:17:268:43 | call to method WriteLine | CSharp7.cs:269:17:269:22 | break; | semmle.label | successor | +| CSharp7.cs:268:17:268:44 | ...; | CSharp7.cs:268:35:268:42 | "Double" | semmle.label | successor | +| CSharp7.cs:268:35:268:42 | "Double" | CSharp7.cs:268:17:268:43 | call to method WriteLine | semmle.label | successor | | CSharp7.cs:269:17:269:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | +| CSharp7.cs:270:13:270:24 | case Object v2: | CSharp7.cs:270:18:270:20 | access to type Object | semmle.label | successor | +| CSharp7.cs:270:18:270:20 | access to type Object | CSharp7.cs:270:18:270:23 | Object v2 | semmle.label | match | +| CSharp7.cs:270:18:270:20 | access to type Object | CSharp7.cs:272:13:272:20 | default: | semmle.label | no-match | +| CSharp7.cs:270:18:270:23 | Object v2 | CSharp7.cs:271:17:271:22 | break; | semmle.label | successor | +| CSharp7.cs:271:17:271:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | +| CSharp7.cs:272:13:272:20 | default: | CSharp7.cs:273:17:273:52 | ...; | semmle.label | successor | +| CSharp7.cs:273:17:273:51 | call to method WriteLine | CSharp7.cs:274:17:274:22 | break; | semmle.label | successor | +| CSharp7.cs:273:17:273:52 | ...; | CSharp7.cs:273:35:273:50 | "Something else" | semmle.label | successor | +| CSharp7.cs:273:35:273:50 | "Something else" | CSharp7.cs:273:17:273:51 | call to method WriteLine | semmle.label | successor | +| CSharp7.cs:274:17:274:22 | break; | CSharp7.cs:231:10:231:13 | exit Test | semmle.label | break | diff --git a/csharp/ql/test/library-tests/csharp7/IsPatterns.expected b/csharp/ql/test/library-tests/csharp7/IsPatterns.expected index b0372f248812..054a9accab33 100644 --- a/csharp/ql/test/library-tests/csharp7/IsPatterns.expected +++ b/csharp/ql/test/library-tests/csharp7/IsPatterns.expected @@ -1,3 +1,4 @@ | CSharp7.cs:234:13:234:23 | ... is ... | CSharp7.cs:234:18:234:20 | access to type Int32 | Int32 | CSharp7.cs:234:18:234:23 | Int32 i1 | false | | CSharp7.cs:238:18:238:31 | ... is ... | CSharp7.cs:238:23:238:28 | access to type String | String | CSharp7.cs:238:23:238:31 | String s1 | false | | CSharp7.cs:245:18:245:28 | ... is ... | CSharp7.cs:245:23:245:25 | access to type Object | Object | CSharp7.cs:245:23:245:28 | Object v1 | true | +| CSharp7.cs:255:27:255:40 | ... is ... | CSharp7.cs:255:32:255:37 | access to type String | String | CSharp7.cs:255:32:255:40 | String s4 | false | diff --git a/csharp/ql/test/library-tests/csharp7/LocalTaintFlow.expected b/csharp/ql/test/library-tests/csharp7/LocalTaintFlow.expected index 0469edd23644..f07a6f7efc86 100644 --- a/csharp/ql/test/library-tests/csharp7/LocalTaintFlow.expected +++ b/csharp/ql/test/library-tests/csharp7/LocalTaintFlow.expected @@ -154,35 +154,42 @@ | CSharp7.cs:242:18:242:18 | access to local variable o | CSharp7.cs:245:18:245:18 | access to local variable o | | CSharp7.cs:242:18:242:18 | access to local variable o | CSharp7.cs:249:17:249:17 | access to local variable o | | CSharp7.cs:245:18:245:18 | access to local variable o | CSharp7.cs:249:17:249:17 | access to local variable o | -| CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:253:18:253:23 | SSA def(i2) | -| CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:256:18:256:23 | SSA def(i3) | -| CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:259:18:259:26 | SSA def(s2) | -| CSharp7.cs:253:18:253:23 | SSA def(i2) | CSharp7.cs:253:30:253:31 | access to local variable i2 | -| CSharp7.cs:253:30:253:31 | access to local variable i2 | CSharp7.cs:253:30:253:35 | ... > ... | -| CSharp7.cs:253:30:253:31 | access to local variable i2 | CSharp7.cs:254:47:254:48 | access to local variable i2 | -| CSharp7.cs:254:37:254:45 | "positive " | CSharp7.cs:254:35:254:50 | $"..." | -| CSharp7.cs:254:47:254:48 | access to local variable i2 | CSharp7.cs:254:35:254:50 | $"..." | -| CSharp7.cs:256:18:256:23 | SSA def(i3) | CSharp7.cs:257:42:257:43 | access to local variable i3 | -| CSharp7.cs:257:37:257:40 | "int " | CSharp7.cs:257:35:257:45 | $"..." | -| CSharp7.cs:257:42:257:43 | access to local variable i3 | CSharp7.cs:257:35:257:45 | $"..." | -| CSharp7.cs:259:18:259:26 | SSA def(s2) | CSharp7.cs:260:45:260:46 | access to local variable s2 | -| CSharp7.cs:260:37:260:43 | "string " | CSharp7.cs:260:35:260:48 | $"..." | -| CSharp7.cs:260:45:260:46 | access to local variable s2 | CSharp7.cs:260:35:260:48 | $"..." | -| CSharp7.cs:278:13:278:48 | SSA def(dict) | CSharp7.cs:279:20:279:23 | access to local variable dict | -| CSharp7.cs:278:20:278:48 | object creation of type Dictionary | CSharp7.cs:278:13:278:48 | SSA def(dict) | -| CSharp7.cs:279:13:279:62 | SSA def(list) | CSharp7.cs:281:39:281:42 | access to local variable list | -| CSharp7.cs:279:20:279:62 | call to method Select | CSharp7.cs:279:13:279:62 | SSA def(list) | -| CSharp7.cs:279:32:279:35 | item | CSharp7.cs:279:41:279:44 | access to parameter item | -| CSharp7.cs:279:32:279:61 | [implicit call] (...) => ... | CSharp7.cs:279:20:279:62 | call to method Select | -| CSharp7.cs:279:41:279:44 | access to parameter item | CSharp7.cs:279:51:279:54 | access to parameter item | -| CSharp7.cs:279:41:279:48 | access to property Key | CSharp7.cs:279:40:279:61 | (..., ...) | -| CSharp7.cs:279:51:279:54 | access to parameter item | CSharp7.cs:279:51:279:60 | access to property Value | -| CSharp7.cs:279:51:279:60 | access to property Value | CSharp7.cs:279:40:279:61 | (..., ...) | -| CSharp7.cs:281:23:281:23 | Int32 a | CSharp7.cs:281:18:281:34 | (..., ...) | -| CSharp7.cs:281:33:281:33 | String b | CSharp7.cs:281:18:281:34 | (..., ...) | -| CSharp7.cs:281:39:281:42 | access to local variable list | CSharp7.cs:283:36:283:39 | access to local variable list | -| CSharp7.cs:283:23:283:23 | Int32 a | CSharp7.cs:283:18:283:31 | (..., ...) | -| CSharp7.cs:283:30:283:30 | String b | CSharp7.cs:283:18:283:31 | (..., ...) | -| CSharp7.cs:283:36:283:39 | access to local variable list | CSharp7.cs:285:32:285:35 | access to local variable list | -| CSharp7.cs:285:23:285:23 | Int32 a | CSharp7.cs:285:18:285:27 | (..., ...) | -| CSharp7.cs:285:26:285:26 | String b | CSharp7.cs:285:18:285:27 | (..., ...) | +| CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:255:27:255:27 | access to local variable o | +| CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:258:18:258:23 | SSA def(i2) | +| CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:261:18:261:23 | SSA def(i3) | +| CSharp7.cs:249:17:249:17 | access to local variable o | CSharp7.cs:264:18:264:26 | SSA def(s2) | +| CSharp7.cs:253:26:253:26 | 1 | CSharp7.cs:253:26:253:30 | ... < ... | +| CSharp7.cs:253:30:253:30 | 2 | CSharp7.cs:253:26:253:30 | ... < ... | +| CSharp7.cs:255:27:255:27 | access to local variable o | CSharp7.cs:255:32:255:40 | SSA def(s4) | +| CSharp7.cs:255:32:255:40 | SSA def(s4) | CSharp7.cs:256:40:256:41 | access to local variable s4 | +| CSharp7.cs:256:37:256:38 | "x " | CSharp7.cs:256:35:256:43 | $"..." | +| CSharp7.cs:256:40:256:41 | access to local variable s4 | CSharp7.cs:256:35:256:43 | $"..." | +| CSharp7.cs:258:18:258:23 | SSA def(i2) | CSharp7.cs:258:30:258:31 | access to local variable i2 | +| CSharp7.cs:258:30:258:31 | access to local variable i2 | CSharp7.cs:258:30:258:35 | ... > ... | +| CSharp7.cs:258:30:258:31 | access to local variable i2 | CSharp7.cs:259:47:259:48 | access to local variable i2 | +| CSharp7.cs:259:37:259:45 | "positive " | CSharp7.cs:259:35:259:50 | $"..." | +| CSharp7.cs:259:47:259:48 | access to local variable i2 | CSharp7.cs:259:35:259:50 | $"..." | +| CSharp7.cs:261:18:261:23 | SSA def(i3) | CSharp7.cs:262:42:262:43 | access to local variable i3 | +| CSharp7.cs:262:37:262:40 | "int " | CSharp7.cs:262:35:262:45 | $"..." | +| CSharp7.cs:262:42:262:43 | access to local variable i3 | CSharp7.cs:262:35:262:45 | $"..." | +| CSharp7.cs:264:18:264:26 | SSA def(s2) | CSharp7.cs:265:45:265:46 | access to local variable s2 | +| CSharp7.cs:265:37:265:43 | "string " | CSharp7.cs:265:35:265:48 | $"..." | +| CSharp7.cs:265:45:265:46 | access to local variable s2 | CSharp7.cs:265:35:265:48 | $"..." | +| CSharp7.cs:283:13:283:48 | SSA def(dict) | CSharp7.cs:284:20:284:23 | access to local variable dict | +| CSharp7.cs:283:20:283:48 | object creation of type Dictionary | CSharp7.cs:283:13:283:48 | SSA def(dict) | +| CSharp7.cs:284:13:284:62 | SSA def(list) | CSharp7.cs:286:39:286:42 | access to local variable list | +| CSharp7.cs:284:20:284:62 | call to method Select | CSharp7.cs:284:13:284:62 | SSA def(list) | +| CSharp7.cs:284:32:284:35 | item | CSharp7.cs:284:41:284:44 | access to parameter item | +| CSharp7.cs:284:32:284:61 | [implicit call] (...) => ... | CSharp7.cs:284:20:284:62 | call to method Select | +| CSharp7.cs:284:41:284:44 | access to parameter item | CSharp7.cs:284:51:284:54 | access to parameter item | +| CSharp7.cs:284:41:284:48 | access to property Key | CSharp7.cs:284:40:284:61 | (..., ...) | +| CSharp7.cs:284:51:284:54 | access to parameter item | CSharp7.cs:284:51:284:60 | access to property Value | +| CSharp7.cs:284:51:284:60 | access to property Value | CSharp7.cs:284:40:284:61 | (..., ...) | +| CSharp7.cs:286:23:286:23 | Int32 a | CSharp7.cs:286:18:286:34 | (..., ...) | +| CSharp7.cs:286:33:286:33 | String b | CSharp7.cs:286:18:286:34 | (..., ...) | +| CSharp7.cs:286:39:286:42 | access to local variable list | CSharp7.cs:288:36:288:39 | access to local variable list | +| CSharp7.cs:288:23:288:23 | Int32 a | CSharp7.cs:288:18:288:31 | (..., ...) | +| CSharp7.cs:288:30:288:30 | String b | CSharp7.cs:288:18:288:31 | (..., ...) | +| CSharp7.cs:288:36:288:39 | access to local variable list | CSharp7.cs:290:32:290:35 | access to local variable list | +| CSharp7.cs:290:23:290:23 | Int32 a | CSharp7.cs:290:18:290:27 | (..., ...) | +| CSharp7.cs:290:26:290:26 | String b | CSharp7.cs:290:18:290:27 | (..., ...) | diff --git a/csharp/ql/test/library-tests/csharp7/LocalVariables.expected b/csharp/ql/test/library-tests/csharp7/LocalVariables.expected index dd1d833c5144..95cfa37a6267 100644 --- a/csharp/ql/test/library-tests/csharp7/LocalVariables.expected +++ b/csharp/ql/test/library-tests/csharp7/LocalVariables.expected @@ -52,15 +52,16 @@ | CSharp7.cs:234:22:234:23 | i1 | int | | CSharp7.cs:238:30:238:31 | s1 | string | | CSharp7.cs:245:27:245:28 | v1 | object | -| CSharp7.cs:253:22:253:23 | i2 | int | -| CSharp7.cs:256:22:256:23 | i3 | int | -| CSharp7.cs:259:25:259:26 | s2 | string | -| CSharp7.cs:265:22:265:23 | v2 | object | -| CSharp7.cs:278:13:278:16 | dict | Dictionary | -| CSharp7.cs:279:13:279:16 | list | IEnumerable<(int, string)> | -| CSharp7.cs:281:23:281:23 | a | int | -| CSharp7.cs:281:33:281:33 | b | string | -| CSharp7.cs:283:23:283:23 | a | int | -| CSharp7.cs:283:30:283:30 | b | string | -| CSharp7.cs:285:23:285:23 | a | int | -| CSharp7.cs:285:26:285:26 | b | string | +| CSharp7.cs:255:39:255:40 | s4 | string | +| CSharp7.cs:258:22:258:23 | i2 | int | +| CSharp7.cs:261:22:261:23 | i3 | int | +| CSharp7.cs:264:25:264:26 | s2 | string | +| CSharp7.cs:270:22:270:23 | v2 | object | +| CSharp7.cs:283:13:283:16 | dict | Dictionary | +| CSharp7.cs:284:13:284:16 | list | IEnumerable<(int, string)> | +| CSharp7.cs:286:23:286:23 | a | int | +| CSharp7.cs:286:33:286:33 | b | string | +| CSharp7.cs:288:23:288:23 | a | int | +| CSharp7.cs:288:30:288:30 | b | string | +| CSharp7.cs:290:23:290:23 | a | int | +| CSharp7.cs:290:26:290:26 | b | string | diff --git a/csharp/ql/test/library-tests/csharp7/TaintReaches.expected b/csharp/ql/test/library-tests/csharp7/TaintReaches.expected index 07f6ec806050..d5d232488ad0 100644 --- a/csharp/ql/test/library-tests/csharp7/TaintReaches.expected +++ b/csharp/ql/test/library-tests/csharp7/TaintReaches.expected @@ -32,6 +32,7 @@ | CSharp7.cs:177:38:177:39 | "" | CSharp7.cs:177:31:177:39 | ... + ... | | CSharp7.cs:236:33:236:36 | "int " | CSharp7.cs:236:31:236:41 | $"..." | | CSharp7.cs:240:33:240:39 | "string " | CSharp7.cs:240:31:240:44 | $"..." | -| CSharp7.cs:254:37:254:45 | "positive " | CSharp7.cs:254:35:254:50 | $"..." | -| CSharp7.cs:257:37:257:40 | "int " | CSharp7.cs:257:35:257:45 | $"..." | -| CSharp7.cs:260:37:260:43 | "string " | CSharp7.cs:260:35:260:48 | $"..." | +| CSharp7.cs:256:37:256:38 | "x " | CSharp7.cs:256:35:256:43 | $"..." | +| CSharp7.cs:259:37:259:45 | "positive " | CSharp7.cs:259:35:259:50 | $"..." | +| CSharp7.cs:262:37:262:40 | "int " | CSharp7.cs:262:35:262:45 | $"..." | +| CSharp7.cs:265:37:265:43 | "string " | CSharp7.cs:265:35:265:48 | $"..." | diff --git a/csharp/ql/test/library-tests/csharp7/TupleAccess.expected b/csharp/ql/test/library-tests/csharp7/TupleAccess.expected index 5f09974c1b52..c825aadfb742 100644 --- a/csharp/ql/test/library-tests/csharp7/TupleAccess.expected +++ b/csharp/ql/test/library-tests/csharp7/TupleAccess.expected @@ -39,7 +39,7 @@ | CSharp7.cs:223:9:223:14 | (..., ...) | write | | CSharp7.cs:224:9:224:18 | (..., ...) | write | | CSharp7.cs:225:9:225:18 | (..., ...) | write | -| CSharp7.cs:279:40:279:61 | (..., ...) | read | -| CSharp7.cs:281:18:281:34 | (..., ...) | read | -| CSharp7.cs:283:18:283:31 | (..., ...) | read | -| CSharp7.cs:285:18:285:27 | (..., ...) | read | +| CSharp7.cs:284:40:284:61 | (..., ...) | read | +| CSharp7.cs:286:18:286:34 | (..., ...) | read | +| CSharp7.cs:288:18:288:31 | (..., ...) | read | +| CSharp7.cs:290:18:290:27 | (..., ...) | read | diff --git a/csharp/ql/test/library-tests/csharp7/TupleExpr.expected b/csharp/ql/test/library-tests/csharp7/TupleExpr.expected index 7c203cfb17cd..ec92d01d1eeb 100644 --- a/csharp/ql/test/library-tests/csharp7/TupleExpr.expected +++ b/csharp/ql/test/library-tests/csharp7/TupleExpr.expected @@ -82,11 +82,11 @@ | CSharp7.cs:224:9:224:18 | (..., ...) | 1 | CSharp7.cs:224:17:224:17 | _ | | CSharp7.cs:225:9:225:18 | (..., ...) | 0 | CSharp7.cs:225:10:225:10 | _ | | CSharp7.cs:225:9:225:18 | (..., ...) | 1 | CSharp7.cs:225:17:225:17 | Double y | -| CSharp7.cs:279:40:279:61 | (..., ...) | 0 | CSharp7.cs:279:41:279:48 | access to property Key | -| CSharp7.cs:279:40:279:61 | (..., ...) | 1 | CSharp7.cs:279:51:279:60 | access to property Value | -| CSharp7.cs:281:18:281:34 | (..., ...) | 0 | CSharp7.cs:281:23:281:23 | Int32 a | -| CSharp7.cs:281:18:281:34 | (..., ...) | 1 | CSharp7.cs:281:33:281:33 | String b | -| CSharp7.cs:283:18:283:31 | (..., ...) | 0 | CSharp7.cs:283:23:283:23 | Int32 a | -| CSharp7.cs:283:18:283:31 | (..., ...) | 1 | CSharp7.cs:283:30:283:30 | String b | -| CSharp7.cs:285:18:285:27 | (..., ...) | 0 | CSharp7.cs:285:23:285:23 | Int32 a | -| CSharp7.cs:285:18:285:27 | (..., ...) | 1 | CSharp7.cs:285:26:285:26 | String b | +| CSharp7.cs:284:40:284:61 | (..., ...) | 0 | CSharp7.cs:284:41:284:48 | access to property Key | +| CSharp7.cs:284:40:284:61 | (..., ...) | 1 | CSharp7.cs:284:51:284:60 | access to property Value | +| CSharp7.cs:286:18:286:34 | (..., ...) | 0 | CSharp7.cs:286:23:286:23 | Int32 a | +| CSharp7.cs:286:18:286:34 | (..., ...) | 1 | CSharp7.cs:286:33:286:33 | String b | +| CSharp7.cs:288:18:288:31 | (..., ...) | 0 | CSharp7.cs:288:23:288:23 | Int32 a | +| CSharp7.cs:288:18:288:31 | (..., ...) | 1 | CSharp7.cs:288:30:288:30 | String b | +| CSharp7.cs:290:18:290:27 | (..., ...) | 0 | CSharp7.cs:290:23:290:23 | Int32 a | +| CSharp7.cs:290:18:290:27 | (..., ...) | 1 | CSharp7.cs:290:26:290:26 | String b | diff --git a/csharp/ql/test/library-tests/csharp7/TupleTypes.expected b/csharp/ql/test/library-tests/csharp7/TupleTypes.expected index ea332f81b55b..f2c7f173f295 100644 --- a/csharp/ql/test/library-tests/csharp7/TupleTypes.expected +++ b/csharp/ql/test/library-tests/csharp7/TupleTypes.expected @@ -31,11 +31,11 @@ | (Int32,Int32,Int32) | (int, int, int) | ValueTuple | 3 | 2 | CSharp7.cs:75:16:75:22 | Item3 | | (Int32,Int32,Int32) | (int, int, int) | ValueTuple | 3 | 2 | CSharp7.cs:75:34:75:34 | Item3 | | (Int32,String) | (int, string) | ValueTuple | 2 | 0 | CSharp7.cs:97:19:97:19 | Item1 | -| (Int32,String) | (int, string) | ValueTuple | 2 | 0 | CSharp7.cs:279:41:279:48 | Key | -| (Int32,String) | (int, string) | ValueTuple | 2 | 0 | CSharp7.cs:281:19:281:23 | a | +| (Int32,String) | (int, string) | ValueTuple | 2 | 0 | CSharp7.cs:284:41:284:48 | Key | +| (Int32,String) | (int, string) | ValueTuple | 2 | 0 | CSharp7.cs:286:19:286:23 | a | | (Int32,String) | (int, string) | ValueTuple | 2 | 1 | CSharp7.cs:97:22:97:37 | Item2 | -| (Int32,String) | (int, string) | ValueTuple | 2 | 1 | CSharp7.cs:279:51:279:60 | Value | -| (Int32,String) | (int, string) | ValueTuple | 2 | 1 | CSharp7.cs:281:26:281:33 | b | +| (Int32,String) | (int, string) | ValueTuple | 2 | 1 | CSharp7.cs:284:51:284:60 | Value | +| (Int32,String) | (int, string) | ValueTuple | 2 | 1 | CSharp7.cs:286:26:286:33 | b | | (String,(Int32,Int32)) | (string, (int, int)) | ValueTuple | 2 | 0 | CSharp7.cs:109:10:109:15 | m1 | | (String,(Int32,Int32)) | (string, (int, int)) | ValueTuple | 2 | 0 | CSharp7.cs:109:29:109:37 | Item1 | | (String,(Int32,Int32)) | (string, (int, int)) | ValueTuple | 2 | 0 | CSharp7.cs:112:10:112:11 | m3 | diff --git a/csharp/ql/test/library-tests/csharp7/TypeCase1.expected b/csharp/ql/test/library-tests/csharp7/TypeCase1.expected index 0c7447b20104..69d12acd3322 100644 --- a/csharp/ql/test/library-tests/csharp7/TypeCase1.expected +++ b/csharp/ql/test/library-tests/csharp7/TypeCase1.expected @@ -1,7 +1,9 @@ | CSharp7.cs:251:13:251:23 | case ...: | -| CSharp7.cs:253:13:253:36 | case Int32 i2: | -| CSharp7.cs:256:13:256:24 | case Int32 i3: | -| CSharp7.cs:259:13:259:27 | case String s2: | -| CSharp7.cs:262:13:262:26 | case Double: | -| CSharp7.cs:265:13:265:24 | case Object v2: | -| CSharp7.cs:267:13:267:20 | default: | +| CSharp7.cs:253:13:253:31 | case ...: | +| CSharp7.cs:255:13:255:41 | case ...: | +| CSharp7.cs:258:13:258:36 | case Int32 i2: | +| CSharp7.cs:261:13:261:24 | case Int32 i3: | +| CSharp7.cs:264:13:264:27 | case String s2: | +| CSharp7.cs:267:13:267:26 | case Double: | +| CSharp7.cs:270:13:270:24 | case Object v2: | +| CSharp7.cs:272:13:272:20 | default: | diff --git a/csharp/ql/test/library-tests/csharp7/TypeCase2.expected b/csharp/ql/test/library-tests/csharp7/TypeCase2.expected index 5352f8e4cc4f..d1ec974ca9c6 100644 --- a/csharp/ql/test/library-tests/csharp7/TypeCase2.expected +++ b/csharp/ql/test/library-tests/csharp7/TypeCase2.expected @@ -1,4 +1,4 @@ -| CSharp7.cs:253:13:253:36 | case Int32 i2: | CSharp7.cs:253:18:253:23 | Int32 i2 | CSharp7.cs:253:18:253:20 | access to type Int32 | Int32 | false | -| CSharp7.cs:256:13:256:24 | case Int32 i3: | CSharp7.cs:256:18:256:23 | Int32 i3 | CSharp7.cs:256:18:256:20 | access to type Int32 | Int32 | false | -| CSharp7.cs:259:13:259:27 | case String s2: | CSharp7.cs:259:18:259:26 | String s2 | CSharp7.cs:259:18:259:23 | access to type String | String | false | -| CSharp7.cs:265:13:265:24 | case Object v2: | CSharp7.cs:265:18:265:23 | Object v2 | CSharp7.cs:265:18:265:20 | access to type Object | Object | true | +| CSharp7.cs:258:13:258:36 | case Int32 i2: | CSharp7.cs:258:18:258:23 | Int32 i2 | CSharp7.cs:258:18:258:20 | access to type Int32 | Int32 | false | +| CSharp7.cs:261:13:261:24 | case Int32 i3: | CSharp7.cs:261:18:261:23 | Int32 i3 | CSharp7.cs:261:18:261:20 | access to type Int32 | Int32 | false | +| CSharp7.cs:264:13:264:27 | case String s2: | CSharp7.cs:264:18:264:26 | String s2 | CSharp7.cs:264:18:264:23 | access to type String | String | false | +| CSharp7.cs:270:13:270:24 | case Object v2: | CSharp7.cs:270:18:270:23 | Object v2 | CSharp7.cs:270:18:270:20 | access to type Object | Object | true | diff --git a/csharp/ql/test/library-tests/csharp7/TypeCase3.expected b/csharp/ql/test/library-tests/csharp7/TypeCase3.expected index bd7583b19d57..1a4fed47cdb7 100644 --- a/csharp/ql/test/library-tests/csharp7/TypeCase3.expected +++ b/csharp/ql/test/library-tests/csharp7/TypeCase3.expected @@ -1 +1 @@ -| CSharp7.cs:253:13:253:36 | case Int32 i2: | CSharp7.cs:253:30:253:35 | ... > ... | +| CSharp7.cs:258:13:258:36 | case Int32 i2: | CSharp7.cs:258:30:258:35 | ... > ... | From c2f3cb6a2a556a2ecb5f7c6b7b1d7f3b54e4fc12 Mon Sep 17 00:00:00 2001 From: calum Date: Wed, 5 Sep 2018 17:07:24 +0100 Subject: [PATCH 30/50] C#: Update analysis change notes. --- change-notes/1.18/analysis-csharp.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/change-notes/1.18/analysis-csharp.md b/change-notes/1.18/analysis-csharp.md index 303c950cf55d..df455c4365bb 100644 --- a/change-notes/1.18/analysis-csharp.md +++ b/change-notes/1.18/analysis-csharp.md @@ -36,6 +36,10 @@ ## Changes to code extraction +* The `into` part of `join` clauses is now extracted. +* The `when` part of constant cases is now extracted. +* Fixed a bug where `while(x is T y) ...` was not extracted correctly. + * *Series of bullet points* ## Changes to QL libraries @@ -59,3 +63,4 @@ - `ControlFlowEdgeGotoCase` has been renamed to `ControlFlow::SuccessorTypes::GotoCaseSuccessor`. - `ControlFlowEdgeGotoDefault` has been renamed to `ControlFlow::SuccessorTypes::GotoDefaultSuccessor`. - `ControlFlowEdgeException` has been renamed to `ControlFlow::SuccessorTypes::ExceptionSuccessor`. +* The predicate `getCondition()` has been moved from `TypeCase` to `CaseStmt`. It is now possible to get the condition of a `ConstCase` using its `getCondition()` predicate. From 3718237acc3211f6402ada129e9d1a675a3e732b Mon Sep 17 00:00:00 2001 From: calum Date: Wed, 5 Sep 2018 18:15:47 +0100 Subject: [PATCH 31/50] C#: Implement CFG for `ConstCase` statements with a condition. --- .../csharp/controlflow/ControlFlowGraph.qll | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index d06c7388266c..ee1277980529 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -1275,6 +1275,14 @@ module ControlFlow { or // Case statement exits with any completion result = lastConstCaseStmt(cc, c) + or + // Condition exists with a `false` completion + result = lastCaseCondition(cc, c) and + c instanceof FalseCompletion + or + // Condition exists abnormally + result = lastCaseCondition(cc, c) and + not c instanceof NormalCompletion ) or cfe = any(TypeCase tc | @@ -1282,11 +1290,11 @@ module ControlFlow { result = lastTypeCaseNoMatch(tc, c) or // Condition exists with a `false` completion - result = lastTypeCaseCondition(tc, c) and + result = lastCaseCondition(tc, c) and c instanceof FalseCompletion or // Condition exists abnormally - result = lastTypeCaseCondition(tc, c) and + result = lastCaseCondition(tc, c) and not c instanceof NormalCompletion or // Case statement exits with any completion @@ -1581,7 +1589,7 @@ module ControlFlow { } pragma [nomagic] - private ControlFlowElement lastTypeCaseCondition(TypeCase tc, Completion c) { + private ControlFlowElement lastCaseCondition(CaseStmt tc, Completion c) { result = last(tc.getCondition(), c) } @@ -2032,9 +2040,9 @@ module ControlFlow { ) or // Flow from last element of condition to next case - exists(TypeCase tc, int i | + exists(CaseStmt tc, int i | tc = ss.getCase(i) | - cfe = lastTypeCaseCondition(tc, c) and + cfe = lastCaseCondition(tc, c) and c instanceof FalseCompletion and result = first(ss.getCase(i + 1)) ) @@ -2064,9 +2072,20 @@ module ControlFlow { c instanceof SimpleCompletion or // Flow from last element of case expression to first element of statement + not exists(cc.getCondition()) and cfe = lastConstCaseExpr(cc, c) and c.(MatchingCompletion).isMatch() and result = first(cc.getStmt()) + or + // Flow from the last element of case expression to the condition + cfe = lastConstCaseExpr(cc, c) and + c.(MatchingCompletion).isMatch() and + result = first(cc.getCondition()) + or + // Flow from last element of case condition to first element of statement + cfe = lastCaseCondition(cc, c) and + c instanceof TrueCompletion and + result = first(cc.getStmt()) ) or exists(TypeCase tc | @@ -2105,7 +2124,7 @@ module ControlFlow { result = first(tc.getStmt()) or // Flow from condition to first element of statement - cfe = lastTypeCaseCondition(tc, c) and + cfe = lastCaseCondition(tc, c) and c instanceof TrueCompletion and result = first(tc.getStmt()) ) From d5eacf8c135dd9618e484157b19df7b56650d6e4 Mon Sep 17 00:00:00 2001 From: calum Date: Thu, 6 Sep 2018 18:20:01 +0100 Subject: [PATCH 32/50] C#: Change expected output. Address review comments. --- csharp/ql/src/semmle/code/csharp/Stmt.qll | 13 +++++------ .../code/csharp/controlflow/Completion.qll | 4 ++-- .../csharp/controlflow/ControlFlowGraph.qll | 4 ++-- .../ql/test/library-tests/linq/Linq1.expected | 22 +++++++++---------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/Stmt.qll b/csharp/ql/src/semmle/code/csharp/Stmt.qll index c72dc3b135ac..44760d9596c4 100644 --- a/csharp/ql/src/semmle/code/csharp/Stmt.qll +++ b/csharp/ql/src/semmle/code/csharp/Stmt.qll @@ -250,24 +250,23 @@ class SwitchStmt extends SelectionStmt, @switch_stmt { * A `case` statement. Either a constant case (`ConstCase`), a type matching * case (`TypeCase`), or a `default` case (`DefaultCase`). */ -class CaseStmt extends Stmt, @case -{ +class CaseStmt extends Stmt, @case { /** * Gets the condition on this case, if any. For example, the type case on line 3 - * has no condition, and the type case on line 4 has condition `s.Length>0`, in + * has no condition, and the type case on line 4 has condition `s.Length > 0`, in * * ``` * switch(p) * { * case int i: - * case string s when s.Length>0: + * case string s when s.Length > 0: * break; * ... * } * ``` */ Expr getCondition() { - result = getChild(2) + result = this.getChild(2) } } @@ -300,13 +299,13 @@ class ConstCase extends LabeledStmt, CaseStmt { /** * A type matching case in a `switch` statement, for example `case int i:` on line 3 or - * `case string s when s.Length>0:` on line 4 in + * `case string s when s.Length > 0:` on line 4 in * * ``` * switch(p) * { * case int i: - * case string s when s.Length>0: + * case string s when s.Length > 0: * break; * ... * } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll index 0ec675d9bbe4..4a22bcc739ed 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll @@ -309,8 +309,8 @@ private predicate inBooleanContext(Expr e, boolean isBooleanCompletionForParent) isBooleanCompletionForParent = false ) or - exists(CaseStmt tc | - tc.getCondition() = e | + exists(CaseStmt cs | + cs.getCondition() = e | isBooleanCompletionForParent = false ) or diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index ee1277980529..019cbd4fb4c3 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -1589,8 +1589,8 @@ module ControlFlow { } pragma [nomagic] - private ControlFlowElement lastCaseCondition(CaseStmt tc, Completion c) { - result = last(tc.getCondition(), c) + private ControlFlowElement lastCaseCondition(CaseStmt cs, Completion c) { + result = last(cs.getCondition(), c) } pragma [nomagic] diff --git a/csharp/ql/test/library-tests/linq/Linq1.expected b/csharp/ql/test/library-tests/linq/Linq1.expected index 8cf5f10679ac..5415468aafd9 100644 --- a/csharp/ql/test/library-tests/linq/Linq1.expected +++ b/csharp/ql/test/library-tests/linq/Linq1.expected @@ -17,21 +17,21 @@ | queries.cs:18:21:21:28 | call to method Select | 1 | queries.cs:21:18:21:27 | (...) => ... | | queries.cs:25:11:25:21 | call to method SelectMany | 0 | queries.cs:24:11:24:25 | IList a = ... | | queries.cs:25:11:25:21 | call to method SelectMany | 1 | queries.cs:25:11:25:21 | IList b = ... | -| queries.cs:25:11:25:21 | call to method SelectMany | 3 | queries.cs:25:21:25:21 | access to local variable a | +| queries.cs:25:11:25:21 | call to method SelectMany | 2 | queries.cs:25:21:25:21 | access to local variable a | | queries.cs:26:11:26:26 | call to method Select | 0 | queries.cs:25:11:25:21 | call to method SelectMany | | queries.cs:26:11:26:26 | call to method Select | 1 | queries.cs:26:11:26:26 | Int32 next = ... | -| queries.cs:26:11:26:26 | call to method Select | 3 | queries.cs:26:22:26:26 | ... + ... | +| queries.cs:26:11:26:26 | call to method Select | 2 | queries.cs:26:22:26:26 | ... + ... | | queries.cs:27:11:27:42 | call to method Join | 0 | queries.cs:26:11:26:26 | call to method Select | | queries.cs:27:11:27:42 | call to method Join | 1 | queries.cs:27:11:27:42 | IList c = ... | -| queries.cs:27:11:27:42 | call to method Join | 3 | queries.cs:27:21:27:25 | access to local variable list1 | -| queries.cs:27:11:27:42 | call to method Join | 4 | queries.cs:27:30:27:33 | access to local variable next | -| queries.cs:27:11:27:42 | call to method Join | 5 | queries.cs:27:42:27:42 | access to local variable c | +| queries.cs:27:11:27:42 | call to method Join | 2 | queries.cs:27:21:27:25 | access to local variable list1 | +| queries.cs:27:11:27:42 | call to method Join | 3 | queries.cs:27:30:27:33 | access to local variable next | +| queries.cs:27:11:27:42 | call to method Join | 4 | queries.cs:27:42:27:42 | access to local variable c | | queries.cs:32:11:32:21 | call to method SelectMany | 0 | queries.cs:31:11:31:25 | IList a = ... | | queries.cs:32:11:32:21 | call to method SelectMany | 1 | queries.cs:32:11:32:21 | IList b = ... | -| queries.cs:32:11:32:21 | call to method SelectMany | 3 | queries.cs:32:21:32:21 | access to local variable a | +| queries.cs:32:11:32:21 | call to method SelectMany | 2 | queries.cs:32:21:32:21 | access to local variable a | | queries.cs:33:11:33:26 | call to method Select | 0 | queries.cs:32:11:32:21 | call to method SelectMany | | queries.cs:33:11:33:26 | call to method Select | 1 | queries.cs:33:11:33:26 | Int32 next = ... | -| queries.cs:33:11:33:26 | call to method Select | 3 | queries.cs:33:22:33:26 | ... + ... | +| queries.cs:33:11:33:26 | call to method Select | 2 | queries.cs:33:22:33:26 | ... + ... | | queries.cs:34:11:34:37 | call to method OrderByDescending | 0 | queries.cs:33:11:33:26 | call to method Select | | queries.cs:34:11:34:37 | call to method OrderByDescending | 1 | queries.cs:34:19:34:26 | ... * ... | | queries.cs:35:11:35:25 | call to method GroupBy | 0 | queries.cs:34:11:34:37 | call to method OrderByDescending | @@ -45,7 +45,7 @@ | queries.cs:51:11:51:18 | call to method Select | 1 | queries.cs:51:18:51:18 | access to local variable a | | queries.cs:55:11:55:49 | call to method GroupJoin | 0 | queries.cs:54:11:54:25 | Int32 a = ... | | queries.cs:55:11:55:49 | call to method GroupJoin | 1 | queries.cs:55:11:55:49 | IList> c = ... | -| queries.cs:55:11:55:49 | call to method GroupJoin | 2 | queries.cs:55:11:55:49 | IList> d = ... | -| queries.cs:55:11:55:49 | call to method GroupJoin | 3 | queries.cs:55:21:55:25 | access to local variable list2 | -| queries.cs:55:11:55:49 | call to method GroupJoin | 4 | queries.cs:55:30:55:30 | access to local variable a | -| queries.cs:55:11:55:49 | call to method GroupJoin | 5 | queries.cs:55:39:55:42 | access to indexer | +| queries.cs:55:11:55:49 | call to method GroupJoin | 2 | queries.cs:55:21:55:25 | access to local variable list2 | +| queries.cs:55:11:55:49 | call to method GroupJoin | 3 | queries.cs:55:30:55:30 | access to local variable a | +| queries.cs:55:11:55:49 | call to method GroupJoin | 4 | queries.cs:55:39:55:42 | access to indexer | +| queries.cs:55:11:55:49 | call to method GroupJoin | 5 | queries.cs:55:11:55:49 | IList> d = ... | From 0cd4340ac3deafad09fdfcc6040f9a6aca214462 Mon Sep 17 00:00:00 2001 From: calum Date: Fri, 7 Sep 2018 10:15:16 +0100 Subject: [PATCH 33/50] C#: Address review comment: refactor `last()` predicate for `ConstCase` and `TypeCase`. --- .../csharp/controlflow/ControlFlowGraph.qll | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index 019cbd4fb4c3..abce0b41b0cb 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -1272,33 +1272,24 @@ module ControlFlow { // Case expression exits abnormally result = lastConstCaseExpr(cc, c) and not c instanceof NormalCompletion - or - // Case statement exits with any completion - result = lastConstCaseStmt(cc, c) - or - // Condition exists with a `false` completion - result = lastCaseCondition(cc, c) and - c instanceof FalseCompletion - or - // Condition exists abnormally - result = lastCaseCondition(cc, c) and - not c instanceof NormalCompletion ) or cfe = any(TypeCase tc | // Type test exits with a non-match result = lastTypeCaseNoMatch(tc, c) - or + ) + or + cfe = any(CaseStmt cs | // Condition exists with a `false` completion - result = lastCaseCondition(tc, c) and + result = lastCaseCondition(cs, c) and c instanceof FalseCompletion or // Condition exists abnormally - result = lastCaseCondition(tc, c) and + result = lastCaseCondition(cs, c) and not c instanceof NormalCompletion or // Case statement exits with any completion - result = lastTypeCaseStmt(tc, c) + result = lastCaseStmt(cs, c) ) or exists(LoopStmt ls | @@ -1584,8 +1575,10 @@ module ControlFlow { } pragma [nomagic] - private ControlFlowElement lastConstCaseStmt(ConstCase cc, Completion c) { - result = last(cc.getStmt(), c) + private ControlFlowElement lastCaseStmt(CaseStmt cs, Completion c) { + result = last(cs.(TypeCase).getStmt(), c) + or + result = last(cs.(ConstCase).getStmt(), c) } pragma [nomagic] @@ -1598,11 +1591,6 @@ module ControlFlow { result = last(tc.getVariableDeclExpr(), c) } - pragma [nomagic] - private ControlFlowElement lastTypeCaseStmt(TypeCase tc, Completion c) { - result = last(tc.getStmt(), c) - } - pragma [nomagic] private ControlFlowElement lastLoopStmtCondition(LoopStmt ls, Completion c) { result = last(ls.getCondition(), c) From 58cf95b15566e9f0a0eb3598416b17c9473ed2f4 Mon Sep 17 00:00:00 2001 From: calum Date: Fri, 7 Sep 2018 11:37:05 +0100 Subject: [PATCH 34/50] C#: Rewrite `not` using `if`. --- .../csharp/controlflow/ControlFlowGraph.qll | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index abce0b41b0cb..e6d31240df8d 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -2059,16 +2059,15 @@ module ControlFlow { result = first(cc.getExpr()) and c instanceof SimpleCompletion or - // Flow from last element of case expression to first element of statement - not exists(cc.getCondition()) and cfe = lastConstCaseExpr(cc, c) and - c.(MatchingCompletion).isMatch() and - result = first(cc.getStmt()) - or - // Flow from the last element of case expression to the condition - cfe = lastConstCaseExpr(cc, c) and - c.(MatchingCompletion).isMatch() and - result = first(cc.getCondition()) + c.(MatchingCompletion).isMatch() and ( + if exists(cc.getCondition()) then + // Flow from the last element of case expression to the condition + result = first(cc.getCondition()) + else + // Flow from last element of case expression to first element of statement + result = first(cc.getStmt()) + ) or // Flow from last element of case condition to first element of statement cfe = lastCaseCondition(cc, c) and From 6aa6b64b813dbcf44bae11f5ea9c2cc04fdc83f5 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Fri, 7 Sep 2018 12:05:40 +0100 Subject: [PATCH 35/50] Remove placeholders and sort table --- change-notes/1.18/analysis-cpp.md | 33 +++++++++++++------------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/change-notes/1.18/analysis-cpp.md b/change-notes/1.18/analysis-cpp.md index 00877a124623..c025702cd4a7 100644 --- a/change-notes/1.18/analysis-cpp.md +++ b/change-notes/1.18/analysis-cpp.md @@ -1,10 +1,5 @@ # Improvements to C/C++ analysis -## General improvements - -> Changes that affect alerts in many files or from many queries -> For example, changes to file classification - ## New queries | **Query** | **Tags** | **Purpose** | @@ -15,22 +10,22 @@ | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| +| Assignment where comparison was intended | Fewer false positive results | Results are no longer reported if the variable is not yet defined. | +| Comparison where assignment was intended | More correct results | "This query now includes results where an overloaded `operator==` is used in the wrong context. | +| For loop variable changed in body | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | +| Local variable hides global variable | Fewer false positive results | Results for parameters are now only reported if the name of the global variable is the same as the name of the parameter as used in the function definition (not just a function declaration). | +| Memory may not be freed | More correct results | This query now models calls to `realloc` more accurately. | +| Nested loops with same variable | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | | Self comparison | Fewer false positive results | Range checks of the form `x == (T)x` are no longer flagged unless they are guaranteed to have the same result on all platforms. | -| [Nested loops with same variable] | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | -| [For loop variable changed in body] | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | -| [Local variable hides global variable] | Fewer false positive results | Results for parameters are now only reported if the name of the global variable is the same as the name of the parameter as used in the function definition (not just a function declaration). | -| [Memory may not be freed] | More correct results | This query now models calls to `realloc` more accurately. | -| Wrong number of arguments to formatting function | Fewer false positive results | Some false positives related to custom printf-like functions have been fixed. | +| Too few arguments to formatting function | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | +| Too many arguments to formatting function | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | +| Use of extreme values in arithmetic expression | Fewer false positives | The query now considers whether a particular expression might cause an overflow of minimum or maximum values only. | +| Use of extreme values in arithmetic expression | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | +| User-controlled data in arithmetic expression | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | +| Variable used in its own initializer | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded | | Wrong number of arguments to formatting function | Clear separation between results of high and low severity | This query has been split into two queries: a high-severity query named [Too few arguments to formatting function] and a low-severity query named [Too many arguments to formatting function]. | -| [Too few arguments to formatting function] | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | -| [Too many arguments to formatting function] | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | -| [Variable used in its own initializer] | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded | -| [Assignment where comparison was intended] | Fewer false positive results | Results are no longer reported if the variable is not yet defined. | -| [Comparison where assignment was intended] | More correct results | "This query now includes results where an overloaded `operator==` is used in the wrong context. | -| [User-controlled data in arithmetic expression] | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | -| [Uncontrolled data in arithmetic expression] | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | -| [Use of extreme values in arithmetic expression] | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | -| [Use of extreme values in arithmetic expression] | Fewer false positives | The query now considers whether a particular expression might cause an overflow of minimum or maximum values only. | +| Wrong number of arguments to formatting function | Fewer false positive results | Some false positives related to custom printf-like functions have been fixed. | +|Uncontrolled data in arithmetic expression | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | ## Changes to QL libraries From e7116f57a035ac25f17a79016c374bcb359e2952 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Fri, 7 Sep 2018 13:24:41 +0100 Subject: [PATCH 36/50] Add query identifiers --- change-notes/1.18/analysis-cpp.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/change-notes/1.18/analysis-cpp.md b/change-notes/1.18/analysis-cpp.md index c025702cd4a7..c1ac6b490ed8 100644 --- a/change-notes/1.18/analysis-cpp.md +++ b/change-notes/1.18/analysis-cpp.md @@ -4,28 +4,28 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| -| Upcast array used in pointer arithmetic | reliability, correctness, external/cwe/cwe-119 | Finds undefined behavior caused by doing pointer arithmetic on an array of objects that has been cast to an array of a supertype. | +| Upcast array used in pointer arithmetic (`cpp/upcast-array-pointer-arithmetic`) | reliability, correctness, external/cwe/cwe-119 | Finds undefined behavior caused by doing pointer arithmetic on an array of objects that has been cast to an array of a supertype. | ## Changes to existing queries | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| -| Assignment where comparison was intended | Fewer false positive results | Results are no longer reported if the variable is not yet defined. | -| Comparison where assignment was intended | More correct results | "This query now includes results where an overloaded `operator==` is used in the wrong context. | -| For loop variable changed in body | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | -| Local variable hides global variable | Fewer false positive results | Results for parameters are now only reported if the name of the global variable is the same as the name of the parameter as used in the function definition (not just a function declaration). | +| Assignment where comparison was intended (`cpp/assign-where-compare-meant`) | Fewer false positive results | Results are no longer reported if the variable is not yet defined. | +| Comparison where assignment was intended (`cpp/compare-where-assign-meant`) | More correct results | "This query now includes results where an overloaded `operator==` is used in the wrong context. | +| For loop variable changed in body (`cpp/loop-variable-changed`) | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | +| Local variable hides global variable (`cpp/local-variable-hides-global-variable`) | Fewer false positive results | Results for parameters are now only reported if the name of the global variable is the same as the name of the parameter as used in the function definition (not just a function declaration). | | Memory may not be freed | More correct results | This query now models calls to `realloc` more accurately. | -| Nested loops with same variable | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | -| Self comparison | Fewer false positive results | Range checks of the form `x == (T)x` are no longer flagged unless they are guaranteed to have the same result on all platforms. | -| Too few arguments to formatting function | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | -| Too many arguments to formatting function | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | +| Nested loops with same variable (`cpp/nested-loops-with-same-variable`) | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | +| Self comparison (`cpp/comparison-of-identical-expressions`) | Fewer false positive results | Range checks of the form `x == (T)x` are no longer flagged unless they are guaranteed to have the same result on all platforms. | +| Too few arguments to formatting function (`cpp/wrong-number-format-arguments`) | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | +| Too many arguments to formatting function (`cpp/too-many-format-arguments`) | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | | Use of extreme values in arithmetic expression | Fewer false positives | The query now considers whether a particular expression might cause an overflow of minimum or maximum values only. | | Use of extreme values in arithmetic expression | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | -| User-controlled data in arithmetic expression | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | -| Variable used in its own initializer | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded | +| User-controlled data in arithmetic expression (`cpp/tainted-arithmetic`) | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | +| Variable used in its own initializer (`cpp/use-in-own-initializer`) | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded | | Wrong number of arguments to formatting function | Clear separation between results of high and low severity | This query has been split into two queries: a high-severity query named [Too few arguments to formatting function] and a low-severity query named [Too many arguments to formatting function]. | | Wrong number of arguments to formatting function | Fewer false positive results | Some false positives related to custom printf-like functions have been fixed. | -|Uncontrolled data in arithmetic expression | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | +|Uncontrolled data in arithmetic expression (`cpp/uncontrolled-arithmetic`) | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | ## Changes to QL libraries From 2e0818d8e66514f361452514736a0b997cfa4d73 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Fri, 7 Sep 2018 13:55:24 +0100 Subject: [PATCH 37/50] Text changes for consistency and clarity --- change-notes/1.18/analysis-cpp.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/change-notes/1.18/analysis-cpp.md b/change-notes/1.18/analysis-cpp.md index c1ac6b490ed8..fc39e76d239d 100644 --- a/change-notes/1.18/analysis-cpp.md +++ b/change-notes/1.18/analysis-cpp.md @@ -11,25 +11,20 @@ | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| | Assignment where comparison was intended (`cpp/assign-where-compare-meant`) | Fewer false positive results | Results are no longer reported if the variable is not yet defined. | -| Comparison where assignment was intended (`cpp/compare-where-assign-meant`) | More correct results | "This query now includes results where an overloaded `operator==` is used in the wrong context. | +| Comparison where assignment was intended (`cpp/compare-where-assign-meant`) | More results | This query now includes results where an overloaded `operator==` is used in the wrong context. | | For loop variable changed in body (`cpp/loop-variable-changed`) | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | | Local variable hides global variable (`cpp/local-variable-hides-global-variable`) | Fewer false positive results | Results for parameters are now only reported if the name of the global variable is the same as the name of the parameter as used in the function definition (not just a function declaration). | -| Memory may not be freed | More correct results | This query now models calls to `realloc` more accurately. | +| Memory may not be freed (`cpp/memory-may-not-be-freed`) | More results | This query now models calls to `realloc` more accurately. | | Nested loops with same variable (`cpp/nested-loops-with-same-variable`) | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | | Self comparison (`cpp/comparison-of-identical-expressions`) | Fewer false positive results | Range checks of the form `x == (T)x` are no longer flagged unless they are guaranteed to have the same result on all platforms. | -| Too few arguments to formatting function (`cpp/wrong-number-format-arguments`) | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | -| Too many arguments to formatting function (`cpp/too-many-format-arguments`) | More correct and fewer false positives results | This query now understands positional format arguments as supported by some libraries. | -| Use of extreme values in arithmetic expression | Fewer false positives | The query now considers whether a particular expression might cause an overflow of minimum or maximum values only. | -| Use of extreme values in arithmetic expression | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | -| User-controlled data in arithmetic expression (`cpp/tainted-arithmetic`) | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | +| Too few arguments to formatting function (`cpp/wrong-number-format-arguments`) | More precise results | This was previously known as "Wrong number of arguments to formatting function". It now focuses only functions calls that are missing arguments, which tend to be more severe. See the next row for the new query that reports lower-severity alerts for calls with too many arguments. In addition, both queries now understand positional format arguments as supported by some libraries, and some false positive results for custom printf-like functions have been fixed.| +| Too many arguments to formatting function (`cpp/too-many-format-arguments`) | More precise results | This new query was created by splitting the old "Wrong number of arguments to formatting function" query (see row above). It reports function calls with too many arguments. | +| Use of extreme values in arithmetic expression (`cpp/arithmetic-with-extreme-values`) | More correct and fewer false positive results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. It also considers whether a particular expression might cause an overflow of minimum or maximum values only. | +| User-controlled data in arithmetic expression (`cpp/tainted-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. | | Variable used in its own initializer (`cpp/use-in-own-initializer`) | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded | -| Wrong number of arguments to formatting function | Clear separation between results of high and low severity | This query has been split into two queries: a high-severity query named [Too few arguments to formatting function] and a low-severity query named [Too many arguments to formatting function]. | -| Wrong number of arguments to formatting function | Fewer false positive results | Some false positives related to custom printf-like functions have been fixed. | -|Uncontrolled data in arithmetic expression (`cpp/uncontrolled-arithmetic`) | More correct results | Increment / decrement / addition assignment / subtraction assignment operations are now understood as arithmetic operations in this query. | +|Uncontrolled data in arithmetic expression (`cpp/uncontrolled-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. | ## Changes to QL libraries -* Fixes for aggregate initializers using designators: - * `ClassAggregateLiteral.getFieldExpr()` previously assumed initializer expressions appeared in the same order as the declaration order of the fields, causing it to associate the expressions with the wrong fields when using designated initializers. This has been fixed. - * `ArrayAggregateLiteral.getElementExpr()` previously assumed initializer expressions appeared in the same order as the corresponding array elements, causing it to associate the expressions with the wrong array elements when using designated initializers. This has been fixed. -* `Element.getEnclosingElement()` no longer includes macro accesses in its results. To explore parents and children of macro accesses, use the relevant member predicates on `MacroAccess` or `MacroInvocation`. +* The `ClassAggregateLiteral.getFieldExpr()` and `ArrayAggregateLiteral.getElementExpr()` predicates incorrectly assumed that initializer expressions appeared in the same order as the declaration order of the fields. This resulted in the association of the expressions with the wrong fields when designated initializers were used. This has been fixed. +* Results for the `Element.getEnclosingElement()` predicate no longer included macro accesses. To explore parents and children of macro accesses, use the relevant member predicates on `MacroAccess` or `MacroInvocation`. From 3eab1de2f5af5a7413f753480af28b89c8556f15 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Fri, 7 Sep 2018 13:56:35 +0100 Subject: [PATCH 38/50] Remove non-LGTM queries from notes (will move to 'studio-cpp.md') --- change-notes/1.18/analysis-cpp.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/change-notes/1.18/analysis-cpp.md b/change-notes/1.18/analysis-cpp.md index fc39e76d239d..93bcf8c09632 100644 --- a/change-notes/1.18/analysis-cpp.md +++ b/change-notes/1.18/analysis-cpp.md @@ -14,12 +14,10 @@ | Comparison where assignment was intended (`cpp/compare-where-assign-meant`) | More results | This query now includes results where an overloaded `operator==` is used in the wrong context. | | For loop variable changed in body (`cpp/loop-variable-changed`) | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | | Local variable hides global variable (`cpp/local-variable-hides-global-variable`) | Fewer false positive results | Results for parameters are now only reported if the name of the global variable is the same as the name of the parameter as used in the function definition (not just a function declaration). | -| Memory may not be freed (`cpp/memory-may-not-be-freed`) | More results | This query now models calls to `realloc` more accurately. | | Nested loops with same variable (`cpp/nested-loops-with-same-variable`) | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | | Self comparison (`cpp/comparison-of-identical-expressions`) | Fewer false positive results | Range checks of the form `x == (T)x` are no longer flagged unless they are guaranteed to have the same result on all platforms. | | Too few arguments to formatting function (`cpp/wrong-number-format-arguments`) | More precise results | This was previously known as "Wrong number of arguments to formatting function". It now focuses only functions calls that are missing arguments, which tend to be more severe. See the next row for the new query that reports lower-severity alerts for calls with too many arguments. In addition, both queries now understand positional format arguments as supported by some libraries, and some false positive results for custom printf-like functions have been fixed.| | Too many arguments to formatting function (`cpp/too-many-format-arguments`) | More precise results | This new query was created by splitting the old "Wrong number of arguments to formatting function" query (see row above). It reports function calls with too many arguments. | -| Use of extreme values in arithmetic expression (`cpp/arithmetic-with-extreme-values`) | More correct and fewer false positive results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. It also considers whether a particular expression might cause an overflow of minimum or maximum values only. | | User-controlled data in arithmetic expression (`cpp/tainted-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. | | Variable used in its own initializer (`cpp/use-in-own-initializer`) | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded | |Uncontrolled data in arithmetic expression (`cpp/uncontrolled-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. | From 9ec2172dcaffd2fc0f9d7087ad29084eab38f08d Mon Sep 17 00:00:00 2001 From: calum Date: Fri, 3 Aug 2018 12:10:34 +0100 Subject: [PATCH 39/50] C#: Fix CFG for unknown expressions, and add a test that also covers object initializer lists fixed by the extractor. --- .../standalone/controlflow/ControlFlow.cs | 14 ++++++++++++++ .../standalone/controlflow/cfg.expected | 18 ++++++++++++++++++ .../standalone/controlflow/cfg.ql | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 csharp/ql/test/library-tests/standalone/controlflow/ControlFlow.cs create mode 100644 csharp/ql/test/library-tests/standalone/controlflow/cfg.expected create mode 100644 csharp/ql/test/library-tests/standalone/controlflow/cfg.ql diff --git a/csharp/ql/test/library-tests/standalone/controlflow/ControlFlow.cs b/csharp/ql/test/library-tests/standalone/controlflow/ControlFlow.cs new file mode 100644 index 000000000000..56a6e5121cbd --- /dev/null +++ b/csharp/ql/test/library-tests/standalone/controlflow/ControlFlow.cs @@ -0,0 +1,14 @@ +// semmle-extractor-options: --standalone + +using System; + +class Cfg +{ + void F() + { + var v = new InvalidType(); + Debug.Assert(v.a.b, "This is true"); + + new CounterCreationData() { CounterHelp = string.Empty, CounterType = v.Type }; + } +} diff --git a/csharp/ql/test/library-tests/standalone/controlflow/cfg.expected b/csharp/ql/test/library-tests/standalone/controlflow/cfg.expected new file mode 100644 index 000000000000..0f211079aa28 --- /dev/null +++ b/csharp/ql/test/library-tests/standalone/controlflow/cfg.expected @@ -0,0 +1,18 @@ +| ControlFlow.cs:7:10:7:10 | enter F | ControlFlow.cs:8:5:13:5 | {...} | +| ControlFlow.cs:8:5:13:5 | {...} | ControlFlow.cs:9:9:9:34 | ... ...; | +| ControlFlow.cs:9:9:9:34 | ... ...; | ControlFlow.cs:9:13:9:13 | access to local variable v | +| ControlFlow.cs:10:9:10:13 | Expression | ControlFlow.cs:10:22:10:22 | access to local variable v | +| ControlFlow.cs:10:9:10:43 | Call to unknown method | ControlFlow.cs:12:9:12:87 | ...; | +| ControlFlow.cs:10:9:10:44 | ...; | ControlFlow.cs:10:9:10:13 | Expression | +| ControlFlow.cs:10:22:10:22 | access to local variable v | ControlFlow.cs:10:22:10:24 | Expression | +| ControlFlow.cs:10:22:10:24 | Expression | ControlFlow.cs:10:22:10:26 | Expression | +| ControlFlow.cs:10:22:10:26 | Expression | ControlFlow.cs:10:29:10:42 | "This is true" | +| ControlFlow.cs:10:29:10:42 | "This is true" | ControlFlow.cs:10:9:10:43 | Call to unknown method | +| ControlFlow.cs:12:35:12:86 | { ..., ... } | ControlFlow.cs:7:10:7:10 | exit F | +| ControlFlow.cs:12:37:12:47 | Expression | ControlFlow.cs:12:51:12:62 | access to field Empty | +| ControlFlow.cs:12:37:12:62 | ... = ... | ControlFlow.cs:12:65:12:75 | Expression | +| ControlFlow.cs:12:51:12:62 | access to field Empty | ControlFlow.cs:12:37:12:62 | ... = ... | +| ControlFlow.cs:12:65:12:75 | Expression | ControlFlow.cs:12:79:12:79 | access to local variable v | +| ControlFlow.cs:12:65:12:84 | ... = ... | ControlFlow.cs:12:35:12:86 | { ..., ... } | +| ControlFlow.cs:12:79:12:79 | access to local variable v | ControlFlow.cs:12:79:12:84 | Expression | +| ControlFlow.cs:12:79:12:84 | Expression | ControlFlow.cs:12:65:12:84 | ... = ... | diff --git a/csharp/ql/test/library-tests/standalone/controlflow/cfg.ql b/csharp/ql/test/library-tests/standalone/controlflow/cfg.ql new file mode 100644 index 000000000000..0d774ab30ab3 --- /dev/null +++ b/csharp/ql/test/library-tests/standalone/controlflow/cfg.ql @@ -0,0 +1,18 @@ +import csharp +import semmle.code.csharp.controlflow.ControlFlowGraph + +/** + * A method call where the target is unknown. + * The purpose of this is to ensure that all MethodCall expressions + * have a valid `toString()`. + */ +class UnknownCall extends MethodCall +{ + UnknownCall() { not exists(this.getTarget()) } + + override string toString() { result = "Call to unknown method" } +} + +query predicate edges(ControlFlowNode n1, ControlFlowNode n2) { + n2 = n1.getASuccessor() +} From ecb3efba34bc8d196471a168adb8ea1fae1f095b Mon Sep 17 00:00:00 2001 From: calum Date: Fri, 7 Sep 2018 18:12:28 +0100 Subject: [PATCH 40/50] C#: Fix merge conflicts. --- .../code/csharp/controlflow/ControlFlowGraph.qll | 10 ++++++++++ .../test/library-tests/standalone/controlflow/cfg.ql | 5 ++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index e6d31240df8d..0079dfd4713b 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -950,6 +950,7 @@ module ControlFlow { not this instanceof CastExpr and not this instanceof AnonymousFunctionExpr and not this instanceof DelegateCall and + not this instanceof @unknown_expr and result = this.getChild(i) or this = any(ExtensionMethodCall emc | @@ -967,6 +968,8 @@ module ControlFlow { result = getCastExprChild(this, i) or result = this.(DelegateCall).getChild(i - 1) + or + result = getUnknownExprChild(this, i) } } @@ -1001,6 +1004,13 @@ module ControlFlow { i = 0 and result = ae.getExpr() } + private ControlFlowElement getUnknownExprChild(@unknown_expr e, int i) { + exists(int c | + result = e.(Expr).getChild(c) | + c = rank[i+1](int j | exists(e.(Expr).getChild(j))) + ) + } + private ControlFlowElement getCastExprChild(CastExpr ce, int i) { // The type access at index 1 is not evaluated at run-time i = 0 and result = ce.getExpr() diff --git a/csharp/ql/test/library-tests/standalone/controlflow/cfg.ql b/csharp/ql/test/library-tests/standalone/controlflow/cfg.ql index 0d774ab30ab3..166ea037b793 100644 --- a/csharp/ql/test/library-tests/standalone/controlflow/cfg.ql +++ b/csharp/ql/test/library-tests/standalone/controlflow/cfg.ql @@ -6,13 +6,12 @@ import semmle.code.csharp.controlflow.ControlFlowGraph * The purpose of this is to ensure that all MethodCall expressions * have a valid `toString()`. */ -class UnknownCall extends MethodCall -{ +class UnknownCall extends MethodCall { UnknownCall() { not exists(this.getTarget()) } override string toString() { result = "Call to unknown method" } } -query predicate edges(ControlFlowNode n1, ControlFlowNode n2) { +query predicate edges(ControlFlow::Node n1, ControlFlow::Node n2) { n2 = n1.getASuccessor() } From a08177f16b254f09004587c60ee14c6f73c51d72 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Sun, 9 Sep 2018 11:52:35 +0100 Subject: [PATCH 41/50] Address initial feebback --- change-notes/1.18/analysis-cpp.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/change-notes/1.18/analysis-cpp.md b/change-notes/1.18/analysis-cpp.md index 93bcf8c09632..b5ad04555431 100644 --- a/change-notes/1.18/analysis-cpp.md +++ b/change-notes/1.18/analysis-cpp.md @@ -16,13 +16,13 @@ | Local variable hides global variable (`cpp/local-variable-hides-global-variable`) | Fewer false positive results | Results for parameters are now only reported if the name of the global variable is the same as the name of the parameter as used in the function definition (not just a function declaration). | | Nested loops with same variable (`cpp/nested-loops-with-same-variable`) | Fewer false positive results | Results where the loop variable is a member of a class or struct now account for the object. | | Self comparison (`cpp/comparison-of-identical-expressions`) | Fewer false positive results | Range checks of the form `x == (T)x` are no longer flagged unless they are guaranteed to have the same result on all platforms. | -| Too few arguments to formatting function (`cpp/wrong-number-format-arguments`) | More precise results | This was previously known as "Wrong number of arguments to formatting function". It now focuses only functions calls that are missing arguments, which tend to be more severe. See the next row for the new query that reports lower-severity alerts for calls with too many arguments. In addition, both queries now understand positional format arguments as supported by some libraries, and some false positive results for custom printf-like functions have been fixed.| +| Too few arguments to formatting function (`cpp/wrong-number-format-arguments`) | More precise results | This was previously known as "Wrong number of arguments to formatting function". It now focuses only on functions calls that are missing arguments, which tend to be more severe. See the next row for the new query that reports lower-severity alerts for calls with too many arguments. In addition, both queries now understand positional format arguments as supported by some libraries, and some false positive results for custom printf-like functions have been fixed.| | Too many arguments to formatting function (`cpp/too-many-format-arguments`) | More precise results | This new query was created by splitting the old "Wrong number of arguments to formatting function" query (see row above). It reports function calls with too many arguments. | -| User-controlled data in arithmetic expression (`cpp/tainted-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. | -| Variable used in its own initializer (`cpp/use-in-own-initializer`) | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded | -|Uncontrolled data in arithmetic expression (`cpp/uncontrolled-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition assignment and subtraction assignment operations. | +| User-controlled data in arithmetic expression (`cpp/tainted-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition-assignment, and subtraction-assignment operations. | +| Variable used in its own initializer (`cpp/use-in-own-initializer`) | Fewer false positive results | Results where a macro is used to indicate deliberate uninitialization are now excluded. | +|Uncontrolled data in arithmetic expression (`cpp/uncontrolled-arithmetic`) | More results | The query is extended to analyze increment, decrement, addition-assignment, and subtraction-assignment operations. | ## Changes to QL libraries -* The `ClassAggregateLiteral.getFieldExpr()` and `ArrayAggregateLiteral.getElementExpr()` predicates incorrectly assumed that initializer expressions appeared in the same order as the declaration order of the fields. This resulted in the association of the expressions with the wrong fields when designated initializers were used. This has been fixed. +* The `ClassAggregateLiteral.getFieldExpr()` and `ArrayAggregateLiteral.getElementExpr()` predicates incorrectly assumed that initializer expressions appeared in the same order as the declaration order of the elements. This resulted in the association of the expressions with the wrong elements when designated initializers were used. This has been fixed. * Results for the `Element.getEnclosingElement()` predicate no longer included macro accesses. To explore parents and children of macro accesses, use the relevant member predicates on `MacroAccess` or `MacroInvocation`. From aaf1ac770db100aa50ad32cfaa4f2e4b9ec2d370 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Sun, 9 Sep 2018 21:30:43 +0200 Subject: [PATCH 42/50] JS: reduce declared precision of js/request-forgery --- change-notes/1.18/analysis-javascript.md | 2 +- javascript/ql/src/Security/CWE-918/RequestForgery.ql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index a7c4cc4b944e..104d45a88bd8 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -102,7 +102,7 @@ | Clear-text logging of sensitive information (`js/clear-text-logging`) | security, external/cwe/cwe-312, external/cwe/cwe-315, external/cwe/cwe-359 | Highlights logging of sensitive information, indicating a violation of [CWE-312](https://cwe.mitre.org/data/definitions/312.html). Results shown on LGTM by default. | | Disabling Electron webSecurity (`js/disabling-electron-websecurity`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `webSecurity` property set to false. Results shown on LGTM by default. | | Enabling Electron allowRunningInsecureContent (`js/enabling-electron-insecure-content`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `allowRunningInsecureContent` property set to true. Results shown on LGTM by default. | -| Uncontrolled data used in remote request (`js/request-forgery`) | security, external/cwe/cwe-918 | Highlights remote requests that are built from unsanitized user input, indicating a violation of [CWE-918](https://cwe.mitre.org/data/definitions/918.html). Results shown on LGTM by default. | +| Uncontrolled data used in remote request (`js/request-forgery`) | security, external/cwe/cwe-918 | Highlights remote requests that are built from unsanitized user input, indicating a violation of [CWE-918](https://cwe.mitre.org/data/definitions/918.html). Results are not shown on LGTM by default. | | Use of externally-controlled format string (`js/tainted-format-string`) | security, external/cwe/cwe-134 | Highlights format strings containing user-provided data, indicating a violation of [CWE-134](https://cwe.mitre.org/data/definitions/134.html). Results shown on LGTM by default. | ## Changes to existing queries diff --git a/javascript/ql/src/Security/CWE-918/RequestForgery.ql b/javascript/ql/src/Security/CWE-918/RequestForgery.ql index a76307c439aa..8035f2ef56fb 100644 --- a/javascript/ql/src/Security/CWE-918/RequestForgery.ql +++ b/javascript/ql/src/Security/CWE-918/RequestForgery.ql @@ -3,7 +3,7 @@ * @description Sending remote requests with user-controlled data allows for request forgery attacks. * @kind problem * @problem.severity error - * @precision high + * @precision medium * @id js/request-forgery * @tags security * external/cwe/cwe-918 From 4473ccdd5e034933bbab6c32ebd14f558f08e9d7 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 6 Sep 2018 10:00:15 +0200 Subject: [PATCH 43/50] Java: Add Mockito.verify to MockitoMockMethod. --- java/ql/src/semmle/code/java/frameworks/Mockito.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/frameworks/Mockito.qll b/java/ql/src/semmle/code/java/frameworks/Mockito.qll index 4944b8ce831f..496cde70aa49 100644 --- a/java/ql/src/semmle/code/java/frameworks/Mockito.qll +++ b/java/ql/src/semmle/code/java/frameworks/Mockito.qll @@ -397,7 +397,7 @@ class MockitoSettableField extends Field { class MockitoMockMethod extends Method { MockitoMockMethod() { this.getDeclaringType().hasQualifiedName("org.mockito", "Mockito") and - this.hasName("mock") + (this.hasName("mock") or this.hasName("verify")) } } From 620f99c5a30ee7c17a15e3766f410169f16b7982 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Tue, 11 Sep 2018 08:14:17 +0100 Subject: [PATCH 44/50] Remove template text --- change-notes/1.18/analysis-csharp.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/change-notes/1.18/analysis-csharp.md b/change-notes/1.18/analysis-csharp.md index df455c4365bb..fe6f9824173e 100644 --- a/change-notes/1.18/analysis-csharp.md +++ b/change-notes/1.18/analysis-csharp.md @@ -1,11 +1,5 @@ # Improvements to C# analysis -> NOTES -> -> Please describe your changes in terms that are suitable for -> customers to read. These notes will have only minor tidying up -> before they are published as part of the release notes. - ## General improvements * Control flow analysis has been improved for `catch` clauses with filters. @@ -14,7 +8,7 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| -| Arbitrary file write during zip extraction ("Zip Slip") (`cs/zipslip`) | security, external/cwe/cwe-022 | Identifies zip extraction routines which allow arbitrary file overwrite vulnerabilities. +| Arbitrary file write during zip extraction ("Zip Slip") (`cs/zipslip`) | security, external/cwe/cwe-022 | Identifies zip extraction routines which allow arbitrary file overwrite vulnerabilities. | | Local scope variable shadows member (`cs/local-shadows-member`) | maintainability, readability | Replaces the existing queries Local variable shadows class member (`cs/local-shadows-class-member`), Local variable shadows struct member (`cs/local-shadows-struct-member`), Parameter shadows class member (`cs/parameter-shadows-class-member`), and Parameter shadows struct member (`cs/parameter-shadows-struct-member`). | ## Changes to existing queries @@ -40,8 +34,6 @@ * The `when` part of constant cases is now extracted. * Fixed a bug where `while(x is T y) ...` was not extracted correctly. -* *Series of bullet points* - ## Changes to QL libraries * A new non-member predicate `mayBeDisposed()` can be used to determine if a variable is potentially disposed inside a library. It will analyse the CIL code in the library to determine this. From f48317f381f23685de93f8296e7367f2873882db Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Tue, 11 Sep 2018 08:27:20 +0100 Subject: [PATCH 45/50] Minor updates to prepare for publication --- change-notes/1.18/analysis-csharp.md | 30 +++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/change-notes/1.18/analysis-csharp.md b/change-notes/1.18/analysis-csharp.md index fe6f9824173e..204064e91482 100644 --- a/change-notes/1.18/analysis-csharp.md +++ b/change-notes/1.18/analysis-csharp.md @@ -15,18 +15,18 @@ | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| -| [Constant condition](https://help.semmle.com/wiki/display/CSHARP/Constant+condition) (`cs/constant-condition`) | More results | The query has been generalized to cover both Null-coalescing left operand is constant (`cs/constant-null-coalescing`) and Switch selector is constant (`cs/constant-switch-selector`). | +| Constant condition (`cs/constant-condition`) | More results | The query has been generalized to report alerts for the old queries Null-coalescing left operand is constant (`cs/constant-null-coalescing`) and Switch selector is constant (`cs/constant-switch-selector`). | | Exposing internal representation (`cs/expose-implementation`) | Different results | The query has been rewritten, based on the [equivalent Java query](https://help.semmle.com/wiki/display/JAVA/Exposing+internal+representation). | -| Local variable shadows class member(`cs/local-shadows-class-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| Local variable shadows struct member (`cs/local-shadows-struct-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| [Missing Dispose call on local IDisposable](https://help.semmle.com/wiki/display/CSHARP/Missing+Dispose+call+on+local+IDisposable) (`cs/local-not-disposed`) | Fewer results | The query identifies more cases where the local variable may be disposed by a library call. | -| [Nested loops with same variable](https://help.semmle.com/wiki/display/CSHARP/Nested+loops+with+same+variable) (`cs/nested-loops-with-same-variable`) | Fewer results | Results are no longer highlighted in nested loops that share the same condition, and do not use the variable after the inner loop. | -| Null-coalescing left operand is constant (`cs/constant-null-coalescing`) | No results | The query has been removed, as it is now covered by Constant condition (`cs/constant-condition`). | -| Parameter shadows class member (`cs/parameter-shadows-class-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| Parameter shadows struct member (`cs/parameter-shadows-struct-member`) | No results | The query has been replaced by Local scope variable shadows member (`cs/local-shadows-member`). | -| [Potentially incorrect CompareTo(...) signature](https://help.semmle.com/wiki/display/CSHARP/Potentially+incorrect+CompareTo%28...%29+signature) (`cs/wrong-compareto-signature`) | Fewer results | Results are no longer highlighted in constructed types. | -| Switch selector is constant (`cs/constant-switch-selector`) | No results | The query has been removed, as it is now covered by Constant condition (`cs/constant-condition`). | -| [Useless upcast](https://help.semmle.com/wiki/display/CSHARP/Useless+upcast) (`cs/useless-upcast`) | Fewer results | The query has been improved to cover more cases where upcasts may be needed. | +| Local variable shadows class member (`cs/local-shadows-class-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Local variable shadows struct member (`cs/local-shadows-struct-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Missing Dispose call on local IDisposable (`cs/local-not-disposed`) | Fewer false positive results | The query identifies more cases where the local variable may be disposed by a library call. | +| Nested loops with same variable (`cs/nested-loops-with-same-variable`) | Fewer false positive results | Results are no longer highlighted in nested loops that share the same condition, and do not use the variable after the inner loop. | +| Null-coalescing left operand is constant (`cs/constant-null-coalescing`) | No results | The query has been removed, as alerts for this problem are now reported by the new query: Constant condition (`cs/constant-condition`). | +| Parameter shadows class member (`cs/parameter-shadows-class-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Parameter shadows struct member (`cs/parameter-shadows-struct-member`) | No results | The query has been replaced by the new query: Local scope variable shadows member (`cs/local-shadows-member`). | +| Potentially incorrect CompareTo(...) signature (`cs/wrong-compareto-signature`) | Fewer false positive results | Results are no longer highlighted in constructed types. | +| Switch selector is constant (`cs/constant-switch-selector`) | No results | The query has been removed, as alerts for this problem are now reported by the new query: Constant condition (`cs/constant-condition`). | +| Useless upcast (`cs/useless-upcast`) | Fewer false positive results | The query has been improved to cover more cases where upcasts may be needed. | ## Changes to code extraction @@ -36,8 +36,9 @@ ## Changes to QL libraries -* A new non-member predicate `mayBeDisposed()` can be used to determine if a variable is potentially disposed inside a library. It will analyse the CIL code in the library to determine this. -* Several control flow graph entities have been renamed (the old names still exist for backwards compatibility): +* A new non-member predicate `mayBeDisposed()` can be used to determine if a variable is potentially disposed inside a library. It will analyze the CIL code in the library to determine this. +* The predicate `getCondition()` has been moved from `TypeCase` to `CaseStmt`. It is now possible to get the condition of a `ConstCase` using its `getCondition()` predicate. +* Several control flow graph entities have been renamed (the old names are deprecated but are still available in this release for backwards compatibility): - `ControlFlowNode` has been renamed to `ControlFlow::Node`. - `CallableEntryNode` has been renamed to `ControlFlow::Nodes::EntryNode`. - `CallableExitNode` has been renamed to `ControlFlow::Nodes::ExitNode`. @@ -55,4 +56,5 @@ - `ControlFlowEdgeGotoCase` has been renamed to `ControlFlow::SuccessorTypes::GotoCaseSuccessor`. - `ControlFlowEdgeGotoDefault` has been renamed to `ControlFlow::SuccessorTypes::GotoDefaultSuccessor`. - `ControlFlowEdgeException` has been renamed to `ControlFlow::SuccessorTypes::ExceptionSuccessor`. -* The predicate `getCondition()` has been moved from `TypeCase` to `CaseStmt`. It is now possible to get the condition of a `ConstCase` using its `getCondition()` predicate. + +> You should update any custom queries that use these entities to ensure that they continue working when the old names are removed in a future release. From 3d444f3dc672497e0b75f266a4f605923422adf9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 6 Sep 2018 10:39:37 +0100 Subject: [PATCH 46/50] JavaScript: fix CFG for EnhancedForStmt --- javascript/ql/src/semmle/javascript/Stmt.qll | 4 ++++ .../DeadStoreOfLocal/for-of-continue.js | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 javascript/ql/test/query-tests/Declarations/DeadStoreOfLocal/for-of-continue.js diff --git a/javascript/ql/src/semmle/javascript/Stmt.qll b/javascript/ql/src/semmle/javascript/Stmt.qll index facc438e12b4..486298a38965 100644 --- a/javascript/ql/src/semmle/javascript/Stmt.qll +++ b/javascript/ql/src/semmle/javascript/Stmt.qll @@ -607,6 +607,10 @@ abstract class EnhancedForLoop extends LoopStmt { override Stmt getBody() { result = getChildStmt(2) } + + override ControlFlowNode getFirstControlFlowNode() { + result = getIteratorExpr().getFirstControlFlowNode() + } } /** A `for`-`in` loop. */ diff --git a/javascript/ql/test/query-tests/Declarations/DeadStoreOfLocal/for-of-continue.js b/javascript/ql/test/query-tests/Declarations/DeadStoreOfLocal/for-of-continue.js new file mode 100644 index 000000000000..da26a3557ade --- /dev/null +++ b/javascript/ql/test/query-tests/Declarations/DeadStoreOfLocal/for-of-continue.js @@ -0,0 +1,13 @@ +function f() { + let y = false; + for (const x of [1, 2, 3]) { + if (x > 0) { + y = true; // OK + continue; + } + return; + } + if (!y) { + console.log("Hello"); + } +} From 0a4a5da1f0aa24578e1915b1263eaf895bbb3996 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 6 Sep 2018 14:18:03 +0100 Subject: [PATCH 47/50] JavaScript: update output of CFG test --- .../ql/test/library-tests/CFG/CFG.expected | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/javascript/ql/test/library-tests/CFG/CFG.expected b/javascript/ql/test/library-tests/CFG/CFG.expected index d2a79ea52a5a..c78a966ed341 100644 --- a/javascript/ql/test/library-tests/CFG/CFG.expected +++ b/javascript/ql/test/library-tests/CFG/CFG.expected @@ -644,15 +644,14 @@ | forof | 1 | entry node of functio ... ;\\n} | 2 | {\\n f ... ;\\n} | | forof | 1 | f | 1 | functio ... ;\\n} | | forof | 1 | functio ... ;\\n} | 10 | exit node of | -| forof | 2 | {\\n f ... ;\\n} | 3 | for (\\n ... )\\n ; | -| forof | 3 | for (\\n ... )\\n ; | 7 | [] | +| forof | 2 | {\\n f ... ;\\n} | 7 | [] | +| forof | 3 | for (\\n ... )\\n ; | 4 | var\\n x | +| forof | 3 | for (\\n ... )\\n ; | 9 | exit node of functio ... ;\\n} | | forof | 4 | var\\n x | 5 | x | | forof | 5 | x | 5 | x | | forof | 5 | x | 8 | ; | -| forof | 7 | [] | 4 | var\\n x | -| forof | 7 | [] | 9 | exit node of functio ... ;\\n} | -| forof | 8 | ; | 4 | var\\n x | -| forof | 8 | ; | 9 | exit node of functio ... ;\\n} | +| forof | 7 | [] | 3 | for (\\n ... )\\n ; | +| forof | 8 | ; | 3 | for (\\n ... )\\n ; | | globals | 1 | entry node of | 14 | g | | globals | 1 | var\\n x,\\n y; | 2 | x | | globals | 2 | x | 2 | x | @@ -662,15 +661,14 @@ | globals | 4 | {\\n var\\n z;\\n} | 5 | var\\n z; | | globals | 5 | var\\n z; | 6 | z | | globals | 6 | z | 6 | z | -| globals | 6 | z | 8 | for (\\n ... [])\\n ; | -| globals | 8 | for (\\n ... [])\\n ; | 11 | [] | +| globals | 6 | z | 11 | [] | +| globals | 8 | for (\\n ... [])\\n ; | 9 | var\\n a | +| globals | 8 | for (\\n ... [])\\n ; | 13 | function\\n g()\\n{\\n} | | globals | 9 | var\\n a | 10 | a | | globals | 10 | a | 10 | a | | globals | 10 | a | 12 | ; | -| globals | 11 | [] | 9 | var\\n a | -| globals | 11 | [] | 13 | function\\n g()\\n{\\n} | -| globals | 12 | ; | 9 | var\\n a | -| globals | 12 | ; | 13 | function\\n g()\\n{\\n} | +| globals | 11 | [] | 8 | for (\\n ... [])\\n ; | +| globals | 12 | ; | 8 | for (\\n ... [])\\n ; | | globals | 13 | entry node of function\\n g()\\n{\\n} | 15 | {\\n} | | globals | 13 | function\\n g()\\n{\\n} | 17 | !\\nfunction\\n h()\\n{\\n}; | | globals | 14 | g | 1 | var\\n x,\\n y; | From 223bf6cf56bb0321175500ff999e43d4ee283a35 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Tue, 11 Sep 2018 22:31:32 +0100 Subject: [PATCH 48/50] Updates for consistency --- change-notes/1.18/analysis-javascript.md | 189 +++++++++++------------ 1 file changed, 93 insertions(+), 96 deletions(-) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index 7278d1e94dc0..01afe4f1fa55 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -2,15 +2,13 @@ ## General improvements -* Additional heuristics have been added to `semmle.javascript.heuristics`. Add `import semmle.javascript.heuristics.all` to a query in order to activate all of the heuristics at once. - -* Modelling of data flow through destructuring assignments has been improved. This may give additional results for the security queries and other queries that rely on data flow. +* Improved modeling of data flow through destructuring assignments may give additional results for the security queries and other queries that rely on data flow. -* Modelling of global variables has been improved. This may give more true-positive results and fewer false-positive results for a variety of queries. +* Improved modeling of global variables may give more true-positive results and fewer false-positive results for a variety of queries. -* Modelling of re-export declarations has been improved. This may result in fewer false-positive results for a variety of queries. +* Improved modeling of re-export declarations may result in fewer false-positive results for a variety of queries. -* Modelling of taint flow through array operations has been improved. This may give additional results for the security queries. +* Improved modeling of taint flow through array operations may give additional results for the security queries. * The taint tracking library recognizes more ways in which taint propagates. In particular, some flow through string formatters is now recognized. This may give additional results for the security queries. @@ -18,84 +16,86 @@ * Type inference for simple function calls has been improved. This may give additional results for queries that rely on type inference. +* Additional heuristics have been added to `semmle.javascript.heuristics`. Add `import semmle.javascript.heuristics.all` to a query in order to activate all of the heuristics at once. + +* Handling of ambient TypeScript code has been improved. As a result, fewer false-positive results will be reported in `.d.ts` files. + * Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following libraries: - - [axios](https://github.com/axios/axios) - - [bluebird](https://bluebirdjs.com) - - [browserid-crypto](https://github.com/mozilla/browserid-crypto) - - [compose-function](https://github.com/stoeffel/compose-function) - - [cookie-parser](https://github.com/expressjs/cookie-parser) - - [cookie-session](https://github.com/expressjs/cookie-session) - - [cross-fetch](https://github.com/lquixada/cross-fetch) - - [crypto-js](https://github.com/https://github.com/brix/crypto-js) - - [deep-assign](https://github.com/sindresorhus/deep-assign) - - [deep-extend](https://github.com/unclechu/node-deep-extend) - - [deep-merge](https://github.com/Raynos/deep-merge) - - [deep](https://github.com/jeffomatic/deep) - - [deepmerge](https://github.com/KyleAMathews/deepmerge) - - [defaults-deep](https://github.com/jonschlinkert/defaults-deep) - - [defaults](https://github.com/tmpvar/defaults) - - [dottie](https://github.com/mickhansen/dottie.js) - - [dotty](https://github.com/deoxxa/dotty) - - [ent](https://github.com/substack/node-ent) - - [entities](https://github.com/fb55/node-entities) - - [escape-goat](https://github.com/sindresorhus/escape-goat) - - [express-jwt](https://github.com/auth0/express-jwt) - - [express-session](https://github.com/expressjs/session) - - [extend-shallow](https://github.com/jonschlinkert/extend-shallow) - - [extend](https://github.com/justmoon/node-extend) - - [extend2](https://github.com/eggjs/extend2) - - [fast-json-parse](https://github.com/mcollina/fast-json-parse) - - [forge](https://github.com/digitalbazaar/forge) - - [format-util](https://github.com/tmpfs/format-util) - - [got](https://github.com/sindresorhus/got) - - [global](https://github.com/Raynos/global) - - [he](https://github.com/mathiasbynens/he) - - [html-entities](https://github.com/mdevils/node-html-entities) - - [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) - - [jquery](https://jquery.com) - - [js-extend](https://github.com/vmattos/js-extend) - - [json-parse-better-errors](https://github.com/zkat/json-parse-better-errors) - - [json-parse-safe](https://github.com/joaquimserafim/json-parse-safe) - - [json-safe-parse](https://github.com/bahamas10/node-json-safe-parse) - - [just-compose](https://github.com/angus-c/just) - - [just-extend](https://github.com/angus-c/just) - - [lodash](https://lodash.com) - - [merge-deep](https://github.com/jonschlinkert/merge-deep) - - [merge-options](https://github.com/schnittstabil/merge-options) - - [merge](https://github.com/yeikos/js.merge) - - [mixin-deep](https://github.com/jonschlinkert/mixin-deep) - - [mixin-object](https://github.com/jonschlinkert/mixin-object) - - [MySQL2](https://github.com/sidorares/node-mysql2) - - [node.extend](https://github.com/dreamerslab/node.extend) - - [node-fetch](https://github.com/bitinn/node-fetch) - - [object-assign](https://github.com/sindresorhus/object-assign) - - [object.assign](https://github.com/ljharb/object.assign) - - [object.defaults](https://github.com/jonschlinkert/object.defaults) - - [parse-json](https://github.com/sindresorhus/parse-json) - - [printf](https://github.com/adaltas/node-printf) - - [printj](https://github.com/SheetJS/printj) - - [q](https://documentup.com/kriskowal/q/) - - [ramda](https://ramdajs.com) - - [request](https://github.com/request/request) - - [request-promise](https://github.com/request/request-promise) - - [request-promise-any](https://github.com/request/request-promise-any) - - [request-promise-native](https://github.com/request/request-promise-native) - - [React Native](https://facebook.github.io/react-native/) - - [safe-json-parse](https://github.com/Raynos/safe-json-parse) - - [sanitize](https://github.com/pocketly/node-sanitize) - - [sanitizer](https://github.com/theSmaw/Caja-HTML-Sanitizer) - - [smart-extend](https://github.com/danielkalen/smart-extend) - - [sprintf.js](https://github.com/alexei/sprintf.js) - - [string-template](https://github.com/Matt-Esch/string-template) - - [superagent](https://github.com/visionmedia/superagent) - - [underscore](https://underscorejs.org) - - [util-extend](https://github.com/isaacs/util-extend) - - [utils-merge](https://github.com/jaredhanson/utils-merge) - - [validator](https://github.com/chriso/validator.js) - - [xss](https://github.com/leizongmin/js-xss) - - [xtend](https://github.com/Raynos/xtend) - -* Handling of ambient TypeScript code has been improved. As a result, fewer false positives will be reported in `.d.ts` files. + [axios](https://github.com/axios/axios), + [bluebird](https://bluebirdjs.com), + [browserid-crypto](https://github.com/mozilla/browserid-crypto), + [compose-function](https://github.com/stoeffel/compose-function), + [cookie-parser](https://github.com/expressjs/cookie-parser), + [cookie-session](https://github.com/expressjs/cookie-session), + [cross-fetch](https://github.com/lquixada/cross-fetch), + [crypto-js](https://github.com/https://github.com/brix/crypto-js), + [deep-assign](https://github.com/sindresorhus/deep-assign), + [deep-extend](https://github.com/unclechu/node-deep-extend), + [deep-merge](https://github.com/Raynos/deep-merge), + [deep](https://github.com/jeffomatic/deep), + [deepmerge](https://github.com/KyleAMathews/deepmerge), + [defaults-deep](https://github.com/jonschlinkert/defaults-deep), + [defaults](https://github.com/tmpvar/defaults), + [dottie](https://github.com/mickhansen/dottie.js), + [dotty](https://github.com/deoxxa/dotty), + [ent](https://github.com/substack/node-ent), + [entities](https://github.com/fb55/node-entities), + [escape-goat](https://github.com/sindresorhus/escape-goat), + [express-jwt](https://github.com/auth0/express-jwt), + [express-session](https://github.com/expressjs/session), + [extend-shallow](https://github.com/jonschlinkert/extend-shallow), + [extend](https://github.com/justmoon/node-extend), + [extend2](https://github.com/eggjs/extend2), + [fast-json-parse](https://github.com/mcollina/fast-json-parse), + [forge](https://github.com/digitalbazaar/forge), + [format-util](https://github.com/tmpfs/format-util), + [got](https://github.com/sindresorhus/got), + [global](https://github.com/Raynos/global), + [he](https://github.com/mathiasbynens/he), + [html-entities](https://github.com/mdevils/node-html-entities), + [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch), + [jquery](https://jquery.com), + [js-extend](https://github.com/vmattos/js-extend), + [json-parse-better-errors](https://github.com/zkat/json-parse-better-errors), + [json-parse-safe](https://github.com/joaquimserafim/json-parse-safe), + [json-safe-parse](https://github.com/bahamas10/node-json-safe-parse), + [just-compose](https://github.com/angus-c/just), + [just-extend](https://github.com/angus-c/just), + [lodash](https://lodash.com), + [merge-deep](https://github.com/jonschlinkert/merge-deep), + [merge-options](https://github.com/schnittstabil/merge-options), + [merge](https://github.com/yeikos/js.merge), + [mixin-deep](https://github.com/jonschlinkert/mixin-deep), + [mixin-object](https://github.com/jonschlinkert/mixin-object), + [MySQL2](https://github.com/sidorares/node-mysql2), + [node.extend](https://github.com/dreamerslab/node.extend), + [node-fetch](https://github.com/bitinn/node-fetch), + [object-assign](https://github.com/sindresorhus/object-assign), + [object.assign](https://github.com/ljharb/object.assign), + [object.defaults](https://github.com/jonschlinkert/object.defaults), + [parse-json](https://github.com/sindresorhus/parse-json), + [printf](https://github.com/adaltas/node-printf), + [printj](https://github.com/SheetJS/printj), + [q](https://documentup.com/kriskowal/q/), + [ramda](https://ramdajs.com), + [request](https://github.com/request/request), + [request-promise](https://github.com/request/request-promise), + [request-promise-any](https://github.com/request/request-promise-any), + [request-promise-native](https://github.com/request/request-promise-native), + [React Native](https://facebook.github.io/react-native/), + [safe-json-parse](https://github.com/Raynos/safe-json-parse), + [sanitize](https://github.com/pocketly/node-sanitize), + [sanitizer](https://github.com/theSmaw/Caja-HTML-Sanitizer), + [smart-extend](https://github.com/danielkalen/smart-extend), + [sprintf.js](https://github.com/alexei/sprintf.js), + [string-template](https://github.com/Matt-Esch/string-template), + [superagent](https://github.com/visionmedia/superagent), + [underscore](https://underscorejs.org), + [util-extend](https://github.com/isaacs/util-extend), + [utils-merge](https://github.com/jaredhanson/utils-merge), + [validator](https://github.com/chriso/validator.js), + [xss](https://github.com/leizongmin/js-xss), + [xtend](https://github.com/Raynos/xtend). ## New queries @@ -104,7 +104,7 @@ | Clear-text logging of sensitive information (`js/clear-text-logging`) | security, external/cwe/cwe-312, external/cwe/cwe-315, external/cwe/cwe-359 | Highlights logging of sensitive information, indicating a violation of [CWE-312](https://cwe.mitre.org/data/definitions/312.html). Results shown on LGTM by default. | | Disabling Electron webSecurity (`js/disabling-electron-websecurity`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `webSecurity` property set to false. Results shown on LGTM by default. | | Enabling Electron allowRunningInsecureContent (`js/enabling-electron-insecure-content`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `allowRunningInsecureContent` property set to true. Results shown on LGTM by default. | -| Uncontrolled data used in remote request (`js/request-forgery`) | security, external/cwe/cwe-918 | Highlights remote requests that are built from unsanitized user input, indicating a violation of [CWE-918](https://cwe.mitre.org/data/definitions/918.html). Results are not shown on LGTM by default. | +| Uncontrolled data used in remote request (`js/request-forgery`) | security, external/cwe/cwe-918 | Highlights remote requests that are built from unsanitized user input, indicating a violation of [CWE-918](https://cwe.mitre.org/data/definitions/918.html). Results are hidden on LGTM by default. | | Use of externally-controlled format string (`js/tainted-format-string`) | security, external/cwe/cwe-134 | Highlights format strings containing user-provided data, indicating a violation of [CWE-134](https://cwe.mitre.org/data/definitions/134.html). Results shown on LGTM by default. | ## Changes to existing queries @@ -112,14 +112,13 @@ | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| | Arguments redefined | Fewer results | This rule previously also flagged redefinitions of `eval`. This was an oversight that is now fixed. | -| Comparison between inconvertible types | Fewer results | This rule now flags fewer comparisons involving parameters. | -| Comparison between inconvertible types | Lower severity | The severity of this rule has been revised to "warning". | +| Comparison between inconvertible types | Fewer results | This rule now flags fewer comparisons involving parameters. The severity of this rule has been revised to "warning". | | CORS misconfiguration for credentials transfer | More true-positive results | This rule now treats header names case-insensitively. | | Hard-coded credentials | More true-positive results | This rule now recognizes secret cryptographic keys. | -| Incomplete string escaping or encoding | Better name, more true-positive results | This rule has been renamed to more clearly reflect its purpose. Also, it now recognizes incomplete URL encoding and decoding. | +| Incomplete string escaping or encoding | New name, more true-positive results | This rule now recognizes incomplete URL encoding and decoding. As a consequence, the name was updated to reflect the change in behavior. | | Insecure randomness | More true-positive results | This rule now recognizes secret cryptographic keys. | | Misleading indentation after control statement | Fewer results | This rule temporarily ignores TypeScript files. | -| Missing rate limiting | More true-positive results, fewer false-positive results | This rule now recognizes additional rate limiters and expensive route handlers. | +| Missing rate limiting | More true-positive results, fewer false-positive results | This rule now recognizes additional rate limiters and expensive route handlers. | | Missing X-Frame-Options HTTP header | Fewer false-positive results | This rule now treats header names case-insensitively. | | Omitted array element | Fewer results | This rule temporarily ignores TypeScript files. | | Reflected cross-site scripting | Fewer false-positive results | This rule now treats header names case-insensitively. | @@ -128,16 +127,14 @@ | Superfluous trailing arguments | Fewer false-positive results | This rule now ignores calls to some empty functions. | | Type confusion through parameter tampering | Fewer false-positive results | This rule no longer flags emptiness checks. | | Uncontrolled command line | More true-positive results | This rule now recognizes indirect command injection through `sh -c` and similar. | -| Unused variable | Fewer results | This rule no longer flags class expressions that could be made anonymous. While technically true, these results are not interesting. | -| Unused variable | Renamed | This rule has been renamed to "Unused variable, import, function or class" to reflect the fact that it flags different kinds of unused program elements. | -| Use of incompletely initialized object| Fewer results | This rule now flags the constructor instead its errorneous `this` or `super` expressions. | -| Useless conditional | Fewer results | This rule no longer flags uses of boolean return values. | -| Useless conditional | Fewer results | This rule now flags fewer comparisons involving parameters. | +| Unused variable | New name, fewer results | This rule has been renamed to "Unused variable, import, function or class" to reflect the fact that it flags different kinds of unused program elements. The rule no longer flags class expressions that could be made anonymous. While technically true, these results are not interesting. | +| Use of incompletely initialized object| Fewer results | This rule now flags the constructor instead of its errorneous `this` or `super` expressions. | +| Useless conditional | Fewer results | This rule no longer flags uses of boolean return values and highlights fewer comparisons involving parameters. | ## Changes to QL libraries -* HTTP and HTTPS requests made using the Node.js `http.request` and `https.request` APIs and the Electron `Electron.net.request` and `Electron.ClientRequest` APIs are modeled as `RemoteFlowSources`. -* HTTP header names are now always normalized to lower case to reflect the fact that they are case insensitive. In particular, the result of `HeaderDefinition.getAHeaderName`, and the first parameter of `HeaderDefinition.defines`, `ExplicitHeaderDefinition.definesExplicitly` and `RouteHandler.getAResponseHeader` is now always a lower-case string. +* HTTP and HTTPS requests made using the Node.js `http.request` and `https.request` APIs, and the Electron `Electron.net.request` and `Electron.ClientRequest` APIs, are modeled as `RemoteFlowSources`. +* HTTP header names are now always normalized to lower case to reflect the fact that they are case insensitive. In particular, the result of `HeaderDefinition.getAHeaderName`, and the first parameter of `HeaderDefinition.defines`, `ExplicitHeaderDefinition.definesExplicitly`, and `RouteHandler.getAResponseHeader` are now always a lower-case string. * New AST nodes have been added for TypeScript 2.9 and 3.0 features. -* The class `JsonParseCall` has been deprecated. Use `JsonParserCall` instead. +* The class `JsonParseCall` has been deprecated. Update your queries to use `JsonParserCall` instead. * The handling of spread arguments in the data flow library has been changed: `DataFlow::InvokeNode.getArgument(i)` is now only defined when there is no spread argument at or before argument position `i`, and similarly `InvokeNode.getNumArgument` is only defined for invocations without spread arguments. From 7dd891d908f327877c8747f3ae1225425eb746e4 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Tue, 11 Sep 2018 22:51:14 +0100 Subject: [PATCH 49/50] Further updates and addition of query @ids --- change-notes/1.18/analysis-javascript.md | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index 01afe4f1fa55..1426db55c916 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -111,25 +111,25 @@ | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| -| Arguments redefined | Fewer results | This rule previously also flagged redefinitions of `eval`. This was an oversight that is now fixed. | -| Comparison between inconvertible types | Fewer results | This rule now flags fewer comparisons involving parameters. The severity of this rule has been revised to "warning". | -| CORS misconfiguration for credentials transfer | More true-positive results | This rule now treats header names case-insensitively. | -| Hard-coded credentials | More true-positive results | This rule now recognizes secret cryptographic keys. | -| Incomplete string escaping or encoding | New name, more true-positive results | This rule now recognizes incomplete URL encoding and decoding. As a consequence, the name was updated to reflect the change in behavior. | -| Insecure randomness | More true-positive results | This rule now recognizes secret cryptographic keys. | -| Misleading indentation after control statement | Fewer results | This rule temporarily ignores TypeScript files. | -| Missing rate limiting | More true-positive results, fewer false-positive results | This rule now recognizes additional rate limiters and expensive route handlers. | -| Missing X-Frame-Options HTTP header | Fewer false-positive results | This rule now treats header names case-insensitively. | -| Omitted array element | Fewer results | This rule temporarily ignores TypeScript files. | -| Reflected cross-site scripting | Fewer false-positive results | This rule now treats header names case-insensitively. | -| Semicolon insertion | Fewer results | This rule temporarily ignores TypeScript files. | -| Server-side URL redirect | More true-positive results | This rule now treats header names case-insensitively. | -| Superfluous trailing arguments | Fewer false-positive results | This rule now ignores calls to some empty functions. | -| Type confusion through parameter tampering | Fewer false-positive results | This rule no longer flags emptiness checks. | -| Uncontrolled command line | More true-positive results | This rule now recognizes indirect command injection through `sh -c` and similar. | -| Unused variable | New name, fewer results | This rule has been renamed to "Unused variable, import, function or class" to reflect the fact that it flags different kinds of unused program elements. The rule no longer flags class expressions that could be made anonymous. While technically true, these results are not interesting. | -| Use of incompletely initialized object| Fewer results | This rule now flags the constructor instead of its errorneous `this` or `super` expressions. | -| Useless conditional | Fewer results | This rule no longer flags uses of boolean return values and highlights fewer comparisons involving parameters. | +| Arguments redefined (`js/arguments-redefinition`) | Fewer results | This query previously also flagged redefinitions of `eval`. This was an oversight that is now fixed. | +| Comparison between inconvertible types (`js/comparison-between-incompatible-types`) | Fewer results | This query now flags fewer comparisons involving parameters. The severity of this query has been revised to "warning". | +| CORS misconfiguration for credentials transfer (`js/cors-misconfiguration-for-credentials`) | More true-positive results | This query now treats header names case-insensitively. | +| Hard-coded credentials (`js/hardcoded-credentials`) | More true-positive results | This query now recognizes secret cryptographic keys. | +| Incomplete string escaping or encoding (`js/incomplete-sanitization`) | New name, more true-positive results | The "Incomplete sanitization" query has been renamed to more clearly reflect its purpose. It now recognizes incomplete URL encoding and decoding. | +| Insecure randomness (`js/insecure-randomness`) | More true-positive results | This query now recognizes secret cryptographic keys. | +| Misleading indentation after control statement (`js/misleading-indentation-after-control-statement`) | Fewer results | This query temporarily ignores TypeScript files. | +| Missing rate limiting (`js/missing-rate-limiting`) | More true-positive results, fewer false-positive results | This query now recognizes additional rate limiters and expensive route handlers. | +| Missing X-Frame-Options HTTP header (`js/missing-x-frame-options`) | Fewer false-positive results | This query now treats header names case-insensitively. | +| Omitted array element (`js/omitted-array-element`)| Fewer results | This query temporarily ignores TypeScript files. | +| Reflected cross-site scripting (`js/reflected-xss`) | Fewer false-positive results | This query now treats header names case-insensitively. | +| Semicolon insertion (`js/automatic-semicolon-insertion`) | Fewer results | This query temporarily ignores TypeScript files. | +| Server-side URL redirect (`js/server-side-unvalidated-url-redirection`) | More true-positive results | This query now treats header names case-insensitively. | +| Superfluous trailing arguments (`js/superfluous-trailing-arguments`) | Fewer false-positive results | This query now ignores calls to some empty functions. | +| Type confusion through parameter tampering (`js/type-confusion-through-parameter-tampering`) | Fewer false-positive results | This query no longer flags emptiness checks. | +| Uncontrolled command line (`js/command-line-injection`) | More true-positive results | This query now recognizes indirect command injection through `sh -c` and similar. | +| Unused variable, import, function or class (`js/unused-local-variable`) | New name, fewer results | The "Unused variable" query has been renamed to reflect the fact that it highlights different kinds of unused program elements. In addition, the query no longer highlights class expressions that could be made anonymous. While technically true, these results are not interesting. | +| Use of incompletely initialized object (`js/incomplete-object-initialization`) | Fewer results | This query now highlights the constructor instead of its erroneous `this` or `super` expressions. | +| Useless conditional (`js/trivial-conditional`) | Fewer results | This query no longer flags uses of boolean return values and highlights fewer comparisons involving parameters. | ## Changes to QL libraries From 4d512a5b010897eacb15a2edf1029c4b6edb74e0 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Tue, 11 Sep 2018 22:54:37 +0100 Subject: [PATCH 50/50] Remove non-LGTM query (see following PR) --- change-notes/1.18/analysis-javascript.md | 1 - 1 file changed, 1 deletion(-) diff --git a/change-notes/1.18/analysis-javascript.md b/change-notes/1.18/analysis-javascript.md index 1426db55c916..4be9e6dbf9e7 100644 --- a/change-notes/1.18/analysis-javascript.md +++ b/change-notes/1.18/analysis-javascript.md @@ -119,7 +119,6 @@ | Insecure randomness (`js/insecure-randomness`) | More true-positive results | This query now recognizes secret cryptographic keys. | | Misleading indentation after control statement (`js/misleading-indentation-after-control-statement`) | Fewer results | This query temporarily ignores TypeScript files. | | Missing rate limiting (`js/missing-rate-limiting`) | More true-positive results, fewer false-positive results | This query now recognizes additional rate limiters and expensive route handlers. | -| Missing X-Frame-Options HTTP header (`js/missing-x-frame-options`) | Fewer false-positive results | This query now treats header names case-insensitively. | | Omitted array element (`js/omitted-array-element`)| Fewer results | This query temporarily ignores TypeScript files. | | Reflected cross-site scripting (`js/reflected-xss`) | Fewer false-positive results | This query now treats header names case-insensitively. | | Semicolon insertion (`js/automatic-semicolon-insertion`) | Fewer results | This query temporarily ignores TypeScript files. |