From abd6df3047a327d62c89a60ff565d2eee8292fa6 Mon Sep 17 00:00:00 2001 From: yh-semmle Date: Wed, 5 Sep 2018 20:16:18 -0400 Subject: [PATCH 01/73] Java: add Semmle/java team to CODEOWNERS --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 503c68f963e..d6d1d4898a6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,3 @@ /csharp/ @Semmle/cs +/java/ @Semmle/java /javascript/ @Semmle/js From 91797012482b1d4b308735febb4307f5a7b77fee Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Thu, 6 Sep 2018 11:38:08 -0700 Subject: [PATCH 02/73] JavaScript: Add query for Node.js integration in Electron framework --- .../Electron/EnablingNodeIntegration.qhelp | 56 +++++++++++++++++++ .../src/Electron/EnablingNodeIntegration.ql | 28 ++++++++++ .../examples/DefaultNodeIntegration.js | 2 + .../examples/EnablingNodeIntegration.js | 26 +++++++++ .../examples/WebViewNodeIntegration.html | 15 +++++ .../EnablingNodeIntegration.expected | 5 ++ .../EnablingNodeIntegration.js | 51 +++++++++++++++++ .../EnablingNodeIntegration.qlref | 1 + 8 files changed, 184 insertions(+) create mode 100644 javascript/ql/src/Electron/EnablingNodeIntegration.qhelp create mode 100644 javascript/ql/src/Electron/EnablingNodeIntegration.ql create mode 100644 javascript/ql/src/Electron/examples/DefaultNodeIntegration.js create mode 100644 javascript/ql/src/Electron/examples/EnablingNodeIntegration.js create mode 100644 javascript/ql/src/Electron/examples/WebViewNodeIntegration.html create mode 100644 javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected create mode 100644 javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js create mode 100644 javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp new file mode 100644 index 00000000000..0e3ad814d43 --- /dev/null +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp @@ -0,0 +1,56 @@ + + + + +

+ Enabling Node.js integration in web content renderers (BrowserWindow, BrowserView and webview) could result in + remote native code execution attacks when rendering malicious JavaScript code from untrusted remote web site or + code that is injected via a cross site scripting vulnerability into the web content under processing. Please note that + the nodeIntegration property is enabled by default in Electron and needs to be set to 'false' explicitly. +

+
+ + +

+ Node.js integration should be disabled when loading remote web sites. If not possible, always set nodeIntegration property + to 'false' before loading remote web sites and only enable it for whitelisted sites. +

+
+ + +

+ The following example shows insecure use of BrowserWindow with regards to nodeIntegration + property: +

+ + +

+ This is problematic, because default value of nodeIntegration is 'true'. +

+ +
+ + +

+ The following example shows insecure and secure uses of tag: +

+ + +
+ + +

+ The following example shows insecure and secure uses of BrowserWindow and BrowserView when + loading untrusted web sites: +

+ + +
+ + + +
  • Electron Documentation: Security, Native Capabilities, and Your Responsibility
  • +
    +
    diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.ql b/javascript/ql/src/Electron/EnablingNodeIntegration.ql new file mode 100644 index 00000000000..39b19c5d43b --- /dev/null +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.ql @@ -0,0 +1,28 @@ +/** + * @name Enabling nodeIntegration and nodeIntegrationInWorker in webPreferences + * @description Enabling nodeIntegration and nodeIntegrationInWorker could expose your app to remote code execution. + * @kind problem + * @problem.severity warning + * @precision very-high + * @tags security + * frameworks/electron + * @id js/enabling-electron-renderer-node-integration + */ + +import javascript + +string checkWebOptions(DataFlow::PropWrite prop, Electron::WebPreferences pref) { + (prop = pref.getAPropertyWrite("nodeIntegration") and + prop.getRhs().mayHaveBooleanValue(true) and + result = "nodeIntegration property may have been enabled on this object that could result in RCE") + or + (prop = pref.getAPropertyWrite("nodeIntegrationInWorker") and + prop.getRhs().mayHaveBooleanValue(true) and + result = "nodeIntegrationInWorker property may have been enabled on this object that could result in RCE") + or + (not exists(pref.asExpr().(ObjectExpr).getPropertyByName("nodeIntegration")) and + result = "nodeIntegration is enabled by default in WebPreferences object that could result in RCE") +} + +from DataFlow::PropWrite property, Electron::WebPreferences preferences +select preferences,checkWebOptions(property, preferences) \ No newline at end of file diff --git a/javascript/ql/src/Electron/examples/DefaultNodeIntegration.js b/javascript/ql/src/Electron/examples/DefaultNodeIntegration.js new file mode 100644 index 00000000000..781d31f59f6 --- /dev/null +++ b/javascript/ql/src/Electron/examples/DefaultNodeIntegration.js @@ -0,0 +1,2 @@ +const win = new BrowserWindow(); +win.loadURL("https://untrusted-site.com"); \ No newline at end of file diff --git a/javascript/ql/src/Electron/examples/EnablingNodeIntegration.js b/javascript/ql/src/Electron/examples/EnablingNodeIntegration.js new file mode 100644 index 00000000000..7a6312a807b --- /dev/null +++ b/javascript/ql/src/Electron/examples/EnablingNodeIntegration.js @@ -0,0 +1,26 @@ +//BAD +win_1 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: true}}); +win_1.loadURL("https://untrusted-site.com"); + +//GOOD +win_2 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: false}}); +win_2.loadURL("https://untrusted-site.com"); + +//BAD +win_3 = new BrowserWindow({ + webPreferences: { + nodeIntegrationInWorker: true + } +}); + +//BAD BrowserView +win_4 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: false}}) +view = new BrowserView({ + webPreferences: { + nodeIntegration: true + } +}); +win.setBrowserView(view); +view.setBounds({ x: 0, y: 0, width: 300, height: 300 }); +view.webContents.loadURL('https://untrusted-site.com'); + diff --git a/javascript/ql/src/Electron/examples/WebViewNodeIntegration.html b/javascript/ql/src/Electron/examples/WebViewNodeIntegration.html new file mode 100644 index 00000000000..1deaa6de332 --- /dev/null +++ b/javascript/ql/src/Electron/examples/WebViewNodeIntegration.html @@ -0,0 +1,15 @@ + + + + + WebView Examples + + + + + + + + + + \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected new file mode 100644 index 00000000000..c2d2cd5684f --- /dev/null +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected @@ -0,0 +1,5 @@ +| EnablingNodeIntegration.js:5:28:11:9 | {\\r\\n ... } | nodeIntegration property may have been enabled on this object that could result in RCE | +| EnablingNodeIntegration.js:5:28:11:9 | {\\r\\n ... } | nodeIntegrationInWorker property may have been enabled on this object that could result in RCE | +| EnablingNodeIntegration.js:15:22:20:9 | {\\r\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | +| EnablingNodeIntegration.js:23:13:27:9 | {\\r\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | +| EnablingNodeIntegration.js:49:71:49:93 | {nodeIn ... : true} | nodeIntegration property may have been enabled on this object that could result in RCE | diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js new file mode 100644 index 00000000000..fd1a4201df7 --- /dev/null +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js @@ -0,0 +1,51 @@ +const {BrowserWindow} = require('electron') + +function test() { + var unsafe_1 = { + webPreferences: { + nodeIntegration: true, + nodeIntegrationInWorker: true, + plugins: true, + webSecurity: true, + sandbox: true + } + }; + + var options_1 = { + webPreferences: { + plugins: true, + nodeIntegrationInWorker: false, + webSecurity: true, + sandbox: true + } + }; + + var pref = { + plugins: true, + webSecurity: true, + sandbox: true + }; + + var options_2 = { + webPreferences: pref, + show: true, + frame: true, + minWidth: 300, + minHeight: 300 + }; + + var safe_used = { + webPreferences: { + nodeIntegration: false, + plugins: true, + webSecurity: true, + sandbox: true + } + }; + + var w1 = new BrowserWindow(unsafe_1); + var w2 = new BrowserWindow(options_1); + var w3 = new BrowserWindow(safe_used); + var w4 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: true}}); + var w5 = new BrowserWindow(options_2); +} \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref new file mode 100644 index 00000000000..3f8dbad0d57 --- /dev/null +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref @@ -0,0 +1 @@ +../../../../src/Electron/EnablingNodeIntegration.ql \ No newline at end of file From ebbd3b3111691edda50b2dd7f2a9074beb14a19a Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Fri, 7 Sep 2018 08:47:35 -0700 Subject: [PATCH 03/73] Adding html encoding to EnablingNodeIntegration.qhelp --- javascript/ql/src/Electron/EnablingNodeIntegration.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp index 0e3ad814d43..03cb7238a74 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp @@ -34,7 +34,7 @@

    - The following example shows insecure and secure uses of tag: + The following example shows insecure and secure uses of <webview> tag:

    From bd92cd14c5e53102904fb7c2f5697dc11a158cb5 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Fri, 7 Sep 2018 09:47:15 -0700 Subject: [PATCH 04/73] Changing EOL in all files to unix format --- javascript/ql/src/Electron/EnablingNodeIntegration.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp index 03cb7238a74..3ac30289f17 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp @@ -34,7 +34,7 @@

    - The following example shows insecure and secure uses of <webview> tag: + The following example shows insecure and secure uses of webview tag:

    From 302e271a790027eee2eb33d97c0d7a9e505caea5 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi <43017278+bnxi@users.noreply.github.com> Date: Fri, 7 Sep 2018 09:52:52 -0700 Subject: [PATCH 05/73] Update EnablingNodeIntegration.expected Change EOL to unix format --- .../NodeIntegration/EnablingNodeIntegration.expected | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected index c2d2cd5684f..f43bc66ef6b 100644 --- a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected @@ -1,5 +1,5 @@ -| EnablingNodeIntegration.js:5:28:11:9 | {\\r\\n ... } | nodeIntegration property may have been enabled on this object that could result in RCE | -| EnablingNodeIntegration.js:5:28:11:9 | {\\r\\n ... } | nodeIntegrationInWorker property may have been enabled on this object that could result in RCE | -| EnablingNodeIntegration.js:15:22:20:9 | {\\r\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | -| EnablingNodeIntegration.js:23:13:27:9 | {\\r\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | +| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegration property may have been enabled on this object that could result in RCE | +| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegrationInWorker property may have been enabled on this object that could result in RCE | +| EnablingNodeIntegration.js:15:22:20:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | +| EnablingNodeIntegration.js:23:13:27:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | | EnablingNodeIntegration.js:49:71:49:93 | {nodeIn ... : true} | nodeIntegration property may have been enabled on this object that could result in RCE | From 43a9d511c22be0535a6d3b454fc556c2d4215c90 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi <43017278+bnxi@users.noreply.github.com> Date: Fri, 7 Sep 2018 14:58:24 -0700 Subject: [PATCH 06/73] Update EnablingNodeIntegration.qhelp --- javascript/ql/src/Electron/EnablingNodeIntegration.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp index 3ac30289f17..47337a03b4e 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp @@ -34,7 +34,7 @@

    - The following example shows insecure and secure uses of webview tag: + The following example shows insecure and secure uses of webview tag:

    From 02047ea26063ed247315d3abde92e78a8ffc1cb9 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Mon, 10 Sep 2018 10:27:29 -0700 Subject: [PATCH 07/73] Edit .expected file --- .../NodeIntegration/EnablingNodeIntegration.expected | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected index f43bc66ef6b..092ad577de3 100644 --- a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected @@ -1,5 +1,5 @@ -| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegration property may have been enabled on this object that could result in RCE | -| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegrationInWorker property may have been enabled on this object that could result in RCE | -| EnablingNodeIntegration.js:15:22:20:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | -| EnablingNodeIntegration.js:23:13:27:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | +| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegration property may have been enabled on this object that could result in RCE | +| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegrationInWorker property may have been enabled on this object that could result in RCE | +| EnablingNodeIntegration.js:15:22:20:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | +| EnablingNodeIntegration.js:23:13:27:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | | EnablingNodeIntegration.js:49:71:49:93 | {nodeIn ... : true} | nodeIntegration property may have been enabled on this object that could result in RCE | From 2d7109b8f5a5b3fc1343c4c40389168f9708dd9c Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 23 Aug 2018 17:45:30 -0400 Subject: [PATCH 08/73] C++: initial implementation of a HashCons library. --- .../code/cpp/valuenumbering/HashCons.qll | 404 ++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll new file mode 100644 index 00000000000..3bfc8654ae1 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -0,0 +1,404 @@ +/** + * Provides an implementation of Hash consing. + * See https://en.wikipedia.org/wiki/Hash_consing + * + * The predicate `hashCons` converts an expression into a `HC`, which is an + * abstract type presenting the hash-cons of the expression. If two + * expressions have the same `HC` then they are structurally equal. + * + * Important note: this library ignores the possibility that the value of + * an expression might change between one occurrence and the next. For + * example: + * + * ``` + * x = a+b; + * a++; + * y = a+b; + * ``` + * + * In this example, both copies of the expression `a+b` will hash-cons to + * the same value, even though the value of `a` has changed. This is the + * intended behavior of this library. If you care about the value of the + * expression being the same, then you should use the GlobalValueNumbering + * library instead. + * + * To determine if the expression `x` is structurally equal to the + * expression `y`, use the library like this: + * + * ``` + * hashCons(x) = hashCons(y) + * ``` + */ + +/* + * Note to developers: the correctness of this module depends on the + * definitions of HC, hashCons, and analyzableExpr being kept in sync with + * each other. If you change this module then make sure that the change is + * symmetric across all three. + */ + +import cpp + +/** Used to represent the hash-cons of an expression. */ +private cached newtype HCBase = + HC_IntConst(int val, Type t) { mk_IntConst(val,t,_) } + or + HC_FloatConst(float val, Type t) { mk_FloatConst(val,t,_) } + or + HC_Variable(Variable x) { + mk_Variable(x, _) + } + or + HC_FieldAccess(HC s, Field f) { + mk_DotFieldAccess(s,f,_) or + mk_PointerFieldAccess_with_deref(s,f,_) or + mk_ImplicitThisFieldAccess_with_deref(s,f,_) + } + or + HC_Deref(HC p) { + mk_Deref(p,_) or + mk_PointerFieldAccess(p,_,_) or + mk_ImplicitThisFieldAccess_with_qualifier(p,_,_) + } + or + HC_ThisExpr(Function fcn) { + mk_ThisExpr(fcn,_) or + mk_ImplicitThisFieldAccess(fcn,_,_) + } + or + HC_Conversion(Type t, HC child) { mk_Conversion(t, child, _) } + or + HC_BinaryOp(HC lhs, HC rhs, string opname) { + mk_BinaryOp(lhs, rhs, opname, _) + } + or + HC_UnaryOp(HC child, string opname) { mk_UnaryOp(child, opname, _) } + or + HC_ArrayAccess(HC x, HC i) { + mk_ArrayAccess(x,i,_) + } + or + // Any expression that is not handled by the cases above is + // given a unique number based on the expression itself. + HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } + +/** + * HC is the hash-cons of an expression. The relationship between `Expr` + * and `HC` is many-to-one: every `Expr` has exactly one `HC`, but multiple + * expressions can have the same `HC`. If two expressions have the same + * `HC`, it means that they are structurally equal. The `HC` is an opaque + * value. The only use for the `HC` of an expression is to find other + * expressions that are structurally equal to it. Use the predicate + * `hashCons` to get the `HC` for an `Expr`. + * + * Note: `HC` has `toString` and `getLocation` methods, so that it can be + * displayed in a results list. These work by picking an arbitrary + * expression with this `HC` and using its `toString` and `getLocation` + * methods. + */ +class HC extends HCBase { + HC() { this instanceof HCBase } + + /** Gets an expression that has this HC. */ + Expr getAnExpr() { + this = hashCons(result) + } + + /** Gets the kind of the HC. This can be useful for debugging. */ + string getKind() { + if this instanceof HC_IntConst then result = "IntConst" else + if this instanceof HC_FloatConst then result = "FloatConst" else + if this instanceof HC_Variable then result = "Variable" else + if this instanceof HC_FieldAccess then result = "FieldAccess" else + if this instanceof HC_Deref then result = "Deref" else + if this instanceof HC_ThisExpr then result = "ThisExpr" else + if this instanceof HC_Conversion then result = "Conversion" else + if this instanceof HC_BinaryOp then result = "BinaryOp" else + if this instanceof HC_UnaryOp then result = "UnaryOp" else + if this instanceof HC_ArrayAccess then result = "ArrayAccess" else + if this instanceof HC_Unanalyzable then result = "Unanalyzable" else + result = "error" + } + + /** + * Gets an example of an expression with this HC. + * This is useful for things like implementing toString(). + */ + private Expr exampleExpr() { + // Pick the expression with the minimum source location string. This is + // just an arbitrary way to pick an expression with this `HC`. + result = + min(Expr e + | this = hashCons(e) + | e order by e.getLocation().toString()) + } + + /** Gets a textual representation of this element. */ + string toString() { + result = exampleExpr().toString() + } + + /** Gets the primary location of this element. */ + Location getLocation() { + result = exampleExpr().getLocation() + } +} + +private predicate analyzableIntConst(Expr e) { + strictcount (e.getValue().toInt()) = 1 and + strictcount (e.getType().getUnspecifiedType()) = 1 +} + +private predicate mk_IntConst(int val, Type t, Expr e) { + analyzableIntConst(e) and + val = e.getValue().toInt() and + t = e.getType().getUnspecifiedType() +} + +private predicate analyzableFloatConst(Expr e) { + strictcount (e.getValue().toFloat()) = 1 and + strictcount (e.getType().getUnspecifiedType()) = 1 and + not analyzableIntConst(e) +} + +private predicate mk_FloatConst(float val, Type t, Expr e) { + analyzableFloatConst(e) and + val = e.getValue().toFloat() and + t = e.getType().getUnspecifiedType() +} + +private predicate analyzableDotFieldAccess(DotFieldAccess access) { + strictcount (access.getTarget()) = 1 and + strictcount (access.getQualifier().getFullyConverted()) = 1 and + not analyzableConst(access) +} + +private predicate mk_DotFieldAccess( + HC qualifier, Field target, DotFieldAccess access) { + analyzableDotFieldAccess(access) and + target = access.getTarget() and + qualifier = hashCons(access.getQualifier().getFullyConverted()) +} + +private predicate analyzablePointerFieldAccess(PointerFieldAccess access) { + strictcount (access.getTarget()) = 1 and + strictcount (access.getQualifier().getFullyConverted()) = 1 and + not analyzableConst(access) +} + +private predicate mk_PointerFieldAccess( + HC qualifier, Field target, + PointerFieldAccess access) { + analyzablePointerFieldAccess(access) and + target = access.getTarget() and + qualifier = hashCons(access.getQualifier().getFullyConverted()) +} + +/* + * `obj->field` is equivalent to `(*obj).field`, so we need to wrap an + * extra `HC_Deref` around the qualifier. + */ +private predicate mk_PointerFieldAccess_with_deref( + HC new_qualifier, Field target, PointerFieldAccess access) { + exists (HC qualifier + | mk_PointerFieldAccess(qualifier, target, access) and + new_qualifier = HC_Deref(qualifier)) +} + +private predicate analyzableImplicitThisFieldAccess( + ImplicitThisFieldAccess access) { + strictcount (access.getTarget()) = 1 and + strictcount (access.getEnclosingFunction()) = 1 and + not analyzableConst(access) +} + +private predicate mk_ImplicitThisFieldAccess( + Function fcn, Field target, + ImplicitThisFieldAccess access) { + analyzableImplicitThisFieldAccess(access) and + target = access.getTarget() and + fcn = access.getEnclosingFunction() +} + +private predicate mk_ImplicitThisFieldAccess_with_qualifier( + HC qualifier, Field target, + ImplicitThisFieldAccess access) { + exists (Function fcn + | mk_ImplicitThisFieldAccess(fcn, target, access) and + qualifier = HC_ThisExpr(fcn)) +} + +private predicate mk_ImplicitThisFieldAccess_with_deref( + HC new_qualifier, Field target, ImplicitThisFieldAccess access) { + exists (HC qualifier + | mk_ImplicitThisFieldAccess_with_qualifier( + qualifier, target, access) and + new_qualifier = HC_Deref(qualifier)) +} + +private predicate analyzableVariable(VariableAccess access) { + not (access instanceof FieldAccess) and + strictcount (access.getTarget()) = 1 and + not analyzableConst(access) +} + +private predicate mk_Variable(Variable x, VariableAccess access) { + analyzableVariable(access) and + x = access.getTarget() +} + +private predicate analyzableConversion(Conversion conv) { + strictcount (conv.getType().getUnspecifiedType()) = 1 and + strictcount (conv.getExpr()) = 1 and + not analyzableConst(conv) +} + +private predicate mk_Conversion(Type t, HC child, Conversion conv) { + analyzableConversion(conv) and + t = conv.getType().getUnspecifiedType() and + child = hashCons(conv.getExpr()) +} + +private predicate analyzableBinaryOp(BinaryOperation op) { + op.isPure() and + strictcount (op.getLeftOperand().getFullyConverted()) = 1 and + strictcount (op.getRightOperand().getFullyConverted()) = 1 and + strictcount (op.getOperator()) = 1 and + not analyzableConst(op) +} + +private predicate mk_BinaryOp( + HC lhs, HC rhs, string opname, BinaryOperation op) { + analyzableBinaryOp(op) and + lhs = hashCons(op.getLeftOperand().getFullyConverted()) and + rhs = hashCons(op.getRightOperand().getFullyConverted()) and + opname = op.getOperator() +} + +private predicate analyzableUnaryOp(UnaryOperation op) { + not (op instanceof PointerDereferenceExpr) and + op.isPure() and + strictcount (op.getOperand().getFullyConverted()) = 1 and + strictcount (op.getOperator()) = 1 and + not analyzableConst(op) +} + +private predicate mk_UnaryOp(HC child, string opname, UnaryOperation op) { + analyzableUnaryOp(op) and + child = hashCons(op.getOperand().getFullyConverted()) and + opname = op.getOperator() +} + +private predicate analyzableThisExpr(ThisExpr thisExpr) { + strictcount(thisExpr.getEnclosingFunction()) = 1 and + not analyzableConst(thisExpr) +} + +private predicate mk_ThisExpr(Function fcn, ThisExpr thisExpr) { + analyzableThisExpr(thisExpr) and + fcn = thisExpr.getEnclosingFunction() +} + +private predicate analyzableArrayAccess(ArrayExpr ae) { + strictcount (ae.getArrayBase().getFullyConverted()) = 1 and + strictcount (ae.getArrayOffset().getFullyConverted()) = 1 and + not analyzableConst(ae) +} + +private predicate mk_ArrayAccess( + HC base, HC offset, ArrayExpr ae) { + analyzableArrayAccess(ae) and + base = hashCons(ae.getArrayBase().getFullyConverted()) and + offset = hashCons(ae.getArrayOffset().getFullyConverted()) +} + +private predicate analyzablePointerDereferenceExpr( + PointerDereferenceExpr deref) { + strictcount (deref.getOperand().getFullyConverted()) = 1 and + not analyzableConst(deref) +} + +private predicate mk_Deref( + HC p, PointerDereferenceExpr deref) { + analyzablePointerDereferenceExpr(deref) and + p = hashCons(deref.getOperand().getFullyConverted()) +} + +/** Gets the hash-cons of expression `e`. */ +cached HC hashCons(Expr e) { + exists (int val, Type t + | mk_IntConst(val, t, e) and + result = HC_IntConst(val, t)) + or + exists (float val, Type t + | mk_FloatConst(val, t, e) and + result = HC_FloatConst(val, t)) + or + // Variable with no SSA information. + exists (Variable x + | mk_Variable(x, e) and + result = HC_Variable(x)) + or + exists (HC qualifier, Field target + | mk_DotFieldAccess(qualifier, target, e) and + result = HC_FieldAccess(qualifier, target)) + or + exists (HC qualifier, Field target + | mk_PointerFieldAccess_with_deref(qualifier, target, e) and + result = HC_FieldAccess(qualifier, target)) + or + exists (HC qualifier, Field target + | mk_ImplicitThisFieldAccess_with_deref(qualifier, target, e) and + result = HC_FieldAccess(qualifier, target)) + or + exists (Function fcn + | mk_ThisExpr(fcn, e) and + result = HC_ThisExpr(fcn)) + or + exists (Type t, HC child + | mk_Conversion(t, child, e) and + result = HC_Conversion(t, child)) + or + exists (HC lhs, HC rhs, string opname + | mk_BinaryOp(lhs, rhs, opname, e) and + result = HC_BinaryOp(lhs, rhs, opname)) + or + exists (HC child, string opname + | mk_UnaryOp(child, opname, e) and + result = HC_UnaryOp(child, opname)) + or + exists (HC x, HC i + | mk_ArrayAccess(x, i, e) and + result = HC_ArrayAccess(x, i)) + or + exists (HC p + | mk_Deref(p, e) and + result = HC_Deref(p)) + or + (not analyzableExpr(e,_) and result = HC_Unanalyzable(e)) +} + +private predicate analyzableConst(Expr e) { + analyzableIntConst(e) or + analyzableFloatConst(e) +} + +/** + * Holds if the expression is explicitly handled by `hashCons`. + * Unanalyzable expressions still need to be given a hash-cons, + * but it will be a unique number that is not shared with any other + * expression. + */ +predicate analyzableExpr(Expr e, string kind) { + (analyzableConst(e) and kind = "Const") or + (analyzableDotFieldAccess(e) and kind = "DotFieldAccess") or + (analyzablePointerFieldAccess(e) and kind = "PointerFieldAccess") or + (analyzableImplicitThisFieldAccess(e) and kind = "ImplicitThisFieldAccess") or + (analyzableVariable(e) and kind = "Variable") or + (analyzableConversion(e) and kind = "Conversion") or + (analyzableBinaryOp(e) and kind = "BinaryOp") or + (analyzableUnaryOp(e) and kind = "UnaryOp") or + (analyzableThisExpr(e) and kind = "ThisExpr") or + (analyzableArrayAccess(e) and kind = "ArrayAccess") or + (analyzablePointerDereferenceExpr(e) and kind = "PointerDereferenceExpr") +} From 3c6a9c08a2ff571e5c9cc604218656dbe522ea92 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 23 Aug 2018 15:25:16 -0700 Subject: [PATCH 09/73] C++: first tests for HashCons --- .../HashCons/GlobalValueNumbering.expected | 35 ++++++ .../HashCons/GlobalValueNumbering.ql | 12 +++ .../HashCons/Uniqueness.expected | 0 .../valuenumbering/HashCons/Uniqueness.ql | 8 ++ .../valuenumbering/HashCons/test.cpp | 100 ++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected create mode 100644 cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.ql create mode 100644 cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.expected create mode 100644 cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql create mode 100644 cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected new file mode 100644 index 00000000000..7807cc607ad --- /dev/null +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected @@ -0,0 +1,35 @@ +| test.cpp:5:3:5:3 | x | 5:c3-c3 6:c3-c3 7:c7-c7 | +| test.cpp:5:7:5:8 | p0 | 5:c7-c8 6:c7-c8 | +| test.cpp:5:7:5:13 | ... + ... | 5:c7-c13 6:c7-c13 | +| test.cpp:5:12:5:13 | p1 | 5:c12-c13 6:c12-c13 | +| test.cpp:16:3:16:3 | x | 16:c3-c3 17:c3-c3 18:c7-c7 | +| test.cpp:16:7:16:8 | p0 | 16:c7-c8 17:c7-c8 | +| test.cpp:16:7:16:13 | ... + ... | 16:c7-c13 17:c7-c13 | +| test.cpp:16:7:16:24 | ... + ... | 16:c7-c24 17:c7-c24 | +| test.cpp:16:12:16:13 | p1 | 16:c12-c13 17:c12-c13 | +| test.cpp:16:17:16:24 | global01 | 16:c17-c24 17:c17-c24 | +| test.cpp:29:3:29:3 | x | 29:c3-c3 31:c3-c3 32:c7-c7 | +| test.cpp:29:7:29:8 | p0 | 29:c7-c8 31:c7-c8 | +| test.cpp:29:7:29:13 | ... + ... | 29:c7-c13 31:c7-c13 | +| test.cpp:29:7:29:24 | ... + ... | 29:c7-c24 31:c7-c24 | +| test.cpp:29:12:29:13 | p1 | 29:c12-c13 31:c12-c13 | +| test.cpp:29:17:29:24 | global02 | 29:c17-c24 31:c17-c24 | +| test.cpp:43:3:43:3 | x | 43:c3-c3 45:c3-c3 46:c7-c7 | +| test.cpp:43:7:43:8 | p0 | 43:c7-c8 45:c7-c8 | +| test.cpp:43:7:43:13 | ... + ... | 43:c7-c13 45:c7-c13 | +| test.cpp:43:7:43:24 | ... + ... | 43:c7-c24 45:c7-c24 | +| test.cpp:43:12:43:13 | p1 | 43:c12-c13 45:c12-c13 | +| test.cpp:43:17:43:24 | global03 | 43:c17-c24 45:c17-c24 | +| test.cpp:44:9:44:9 | 0 | 44:c9-c9 51:c25-c25 53:c18-c21 56:c39-c42 59:c17-c20 88:c12-c12 | +| test.cpp:53:10:53:13 | (int)... | 53:c10-c13 56:c21-c24 | +| test.cpp:53:10:53:13 | * ... | 53:c10-c13 56:c21-c24 | +| test.cpp:53:11:53:13 | str | 53:c11-c13 56:c22-c24 | +| test.cpp:53:18:53:21 | 0 | 53:c18-c21 56:c39-c42 59:c17-c20 | +| test.cpp:55:5:55:7 | ptr | 55:c5-c7 56:c14-c16 56:c32-c34 56:c47-c49 59:c10-c12 | +| test.cpp:56:13:56:16 | (int)... | 56:c13-c16 56:c31-c34 59:c9-c12 | +| test.cpp:56:13:56:16 | * ... | 56:c13-c16 56:c31-c34 59:c9-c12 | +| test.cpp:62:5:62:10 | result | 62:c5-c10 65:c10-c15 | +| test.cpp:79:7:79:7 | v | 79:c7-c7 80:c5-c5 | +| test.cpp:79:11:79:14 | vals | 79:c11-c14 79:c24-c27 | +| test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | +| test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.ql b/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.ql new file mode 100644 index 00000000000..edd5ae2c63b --- /dev/null +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.ql @@ -0,0 +1,12 @@ +import cpp +import semmle.code.cpp.valuenumbering.HashCons + +from HC h +where strictcount(h.getAnExpr()) > 1 +select + h, + strictconcat(Location loc + | loc = h.getAnExpr().getLocation() + | loc.getStartLine() + + ":c" + loc.getStartColumn() + "-c" + loc.getEndColumn() + , " ") diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql b/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql new file mode 100644 index 00000000000..f0ee6d237d3 --- /dev/null +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql @@ -0,0 +1,8 @@ +import cpp +import semmle.code.cpp.valuenumbering.HashCons + +// Every expression should have exactly one GVN. +// So this query should have zero results. +from Expr e +where count(hashCons(e)) != 1 +select e, concat(HC h | h = hashCons(e) | h.getKind(), ", ") diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp new file mode 100644 index 00000000000..45089be9fef --- /dev/null +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -0,0 +1,100 @@ +int test00(int p0, int p1) { + int x, y; + unsigned char b; + + x = p0 + p1; + x = p0 + p1; // Same value as previous line. Should the assignment also be matched? + y = x; +} + +int global01 = 1; + +int test01(int p0, int p1) { + int x, y; + unsigned char b; + + x = p0 + p1 + global01; + x = p0 + p1 + global01; // Same structure as previous line. + y = x; // x is the same as x above +} + +int global02 = 2; + +void change_global02(); // Just a declaration + +int test02(int p0, int p1) { + int x, y; + unsigned char b; + + x = p0 + p1 + global02; + change_global02(); + x = p0 + p1 + global02; // same HashCons as above + y = x; +} + +int global03 = 3; + +void change_global03(); // Just a declaration + +int test03(int p0, int p1, int* p2) { + int x, y; + unsigned char b; + + x = p0 + p1 + global03; + *p2 = 0; + x = p0 + p1 + global03; // same HashCons as 43 + y = x; +} + +unsigned int my_strspn(const char *str, const char *chars) { + const char *ptr; + unsigned int result = 0; + + while (*str != '\0') { + // check *str against chars + ptr = chars; + while ((*ptr != *str) && (*ptr != '\0')) {ptr++;} + + // update + if (*ptr == '\0') { // ptr same as ptr on lines 53 and 56 + break; + } + result++; + } + + return result; // result same as result on line 62 +} + +int getAValue(); + +struct two_values { + signed short val1; + signed short val2; +}; + +void test04(two_values *vals) +{ + signed short v = getAValue(); // should this match getAValue() on line 80? + + if (v < vals->val1 + vals->val2) { + v = getAValue(); // should this match getAValue() on line 77? + } +} + +void test05(int x, int y, void *p) +{ + int v; + + v = p != 0 ? x : y; +} + +int regression_test00() { + int x = x = 10; + return x; +} + +void test06(int x) { + x++; + x++; // x is matched but x++ is not matched? +} + From 8b8ec7c5aa892f6856c4d3672f45a6a0951d8976 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 23 Aug 2018 15:30:58 -0700 Subject: [PATCH 10/73] C++: add literal tests --- .../HashCons/GlobalValueNumbering.expected | 16 ++++++++++++++++ .../valuenumbering/HashCons/test.cpp | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected index 7807cc607ad..8a350ba2f27 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected @@ -33,3 +33,19 @@ | test.cpp:79:11:79:14 | vals | 79:c11-c14 79:c24-c27 | | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 | +| test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | +| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 | +| test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 35:c16-c16 | +| test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | +| test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | +| test.cpp:110:15:110:17 | array to pointer conversion | 110:c15-c17 111:c9-c11 | +| test.cpp:111:3:111:5 | str | 111:c3-c5 112:c3-c5 113:c3-c5 | +| test.cpp:112:9:112:11 | 2 | 112:c9-c11 113:c9-c11 | +| test.cpp:112:9:112:11 | (char *)... | 112:c9-c11 113:c9-c11 | +| test.cpp:112:9:112:11 | array to pointer conversion | 112:c9-c11 113:c9-c11 | +| test.cpp:115:13:115:15 | 0.0 | 115:c13-c15 116:c7-c9 | +| test.cpp:115:13:115:15 | (float)... | 115:c13-c15 116:c7-c9 | +| test.cpp:116:3:116:3 | y | 116:c3-c3 117:c3-c3 118:c3-c3 | +| test.cpp:117:7:117:9 | 0.10000000000000001 | 117:c7-c9 118:c7-c9 | +| test.cpp:117:7:117:9 | (float)... | 117:c7-c9 118:c7-c9 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 45089be9fef..d672f66e833 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -98,3 +98,22 @@ void test06(int x) { x++; // x is matched but x++ is not matched? } +// literals +void test07() { + int x = 1; + x = 1; + x = 2; + x = 2; + x = 1 + 2; + x = 1 + 2; + + char *str = "1"; + str = "1"; + str = "2"; + str = "2"; + + float y = 0.0; + y = 0.0; + y = 0.1; + y = 0.1; +} From d8dc75abf4086ca0ae6dd70ef83d3c4a939249cd Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 23 Aug 2018 15:32:46 -0700 Subject: [PATCH 11/73] C++: rename HashCons test --- .../HashCons/{GlobalValueNumbering.expected => HashCons.expected} | 0 .../HashCons/{GlobalValueNumbering.ql => HashCons.ql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cpp/ql/test/library-tests/valuenumbering/HashCons/{GlobalValueNumbering.expected => HashCons.expected} (100%) rename cpp/ql/test/library-tests/valuenumbering/HashCons/{GlobalValueNumbering.ql => HashCons.ql} (100%) diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected similarity index 100% rename from cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.expected rename to cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.ql b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.ql similarity index 100% rename from cpp/ql/test/library-tests/valuenumbering/HashCons/GlobalValueNumbering.ql rename to cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.ql From cf222c51ac33a798beeb0617e3bc0614f702020c Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 23 Aug 2018 16:47:01 -0700 Subject: [PATCH 12/73] C++: treat constant-valued exprs structurally --- .../code/cpp/valuenumbering/HashCons.qll | 98 ++++++++++--------- .../valuenumbering/HashCons/HashCons.expected | 5 +- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 3bfc8654ae1..9c468178ee9 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -41,9 +41,11 @@ import cpp /** Used to represent the hash-cons of an expression. */ private cached newtype HCBase = - HC_IntConst(int val, Type t) { mk_IntConst(val,t,_) } + HC_IntLiteral(int val, Type t) { mk_IntLiteral(val,t,_) } or - HC_FloatConst(float val, Type t) { mk_FloatConst(val,t,_) } + HC_FloatLiteral(float val, Type t) { mk_FloatLiteral(val,t,_) } + or + HC_StringLiteral(string val, Type t) {mk_StringLiteral(val,t,_)} or HC_Variable(Variable x) { mk_Variable(x, _) @@ -106,8 +108,9 @@ class HC extends HCBase { /** Gets the kind of the HC. This can be useful for debugging. */ string getKind() { - if this instanceof HC_IntConst then result = "IntConst" else - if this instanceof HC_FloatConst then result = "FloatConst" else + if this instanceof HC_IntLiteral then result = "IntLiteral" else + if this instanceof HC_FloatLiteral then result = "FloatLiteral" else + if this instanceof HC_StringLiteral then result = "StringLiteral" else if this instanceof HC_Variable then result = "Variable" else if this instanceof HC_FieldAccess then result = "FieldAccess" else if this instanceof HC_Deref then result = "Deref" else @@ -144,33 +147,45 @@ class HC extends HCBase { } } -private predicate analyzableIntConst(Expr e) { +private predicate analyzableIntLiteral(Literal e) { strictcount (e.getValue().toInt()) = 1 and strictcount (e.getType().getUnspecifiedType()) = 1 } -private predicate mk_IntConst(int val, Type t, Expr e) { - analyzableIntConst(e) and +private predicate mk_IntLiteral(int val, Type t, Expr e) { + analyzableIntLiteral(e) and val = e.getValue().toInt() and - t = e.getType().getUnspecifiedType() + t = e.getType().getUnspecifiedType() and + t instanceof IntegralType } - -private predicate analyzableFloatConst(Expr e) { +private predicate analyzableFloatLiteral(Literal e) { strictcount (e.getValue().toFloat()) = 1 and - strictcount (e.getType().getUnspecifiedType()) = 1 and - not analyzableIntConst(e) + strictcount (e.getType().getUnspecifiedType()) = 1 } -private predicate mk_FloatConst(float val, Type t, Expr e) { - analyzableFloatConst(e) and +private predicate mk_FloatLiteral(float val, Type t, Expr e) { + analyzableFloatLiteral(e) and val = e.getValue().toFloat() and - t = e.getType().getUnspecifiedType() + t = e.getType().getUnspecifiedType() and + t instanceof FloatingPointType +} + +private predicate analyzableStringLiteral(Literal e) { + strictcount(e.getValue()) = 1 and + strictcount(e.getType().getUnspecifiedType()) = 1 +} + +private predicate mk_StringLiteral(string val, Type t, Expr e) { + analyzableStringLiteral(e) and + val = e.getValue() and + t = e.getType().getUnspecifiedType() and + t.(ArrayType).getBaseType() instanceof CharType + } private predicate analyzableDotFieldAccess(DotFieldAccess access) { strictcount (access.getTarget()) = 1 and - strictcount (access.getQualifier().getFullyConverted()) = 1 and - not analyzableConst(access) + strictcount (access.getQualifier().getFullyConverted()) = 1 } private predicate mk_DotFieldAccess( @@ -182,8 +197,7 @@ private predicate mk_DotFieldAccess( private predicate analyzablePointerFieldAccess(PointerFieldAccess access) { strictcount (access.getTarget()) = 1 and - strictcount (access.getQualifier().getFullyConverted()) = 1 and - not analyzableConst(access) + strictcount (access.getQualifier().getFullyConverted()) = 1 } private predicate mk_PointerFieldAccess( @@ -208,8 +222,7 @@ private predicate mk_PointerFieldAccess_with_deref( private predicate analyzableImplicitThisFieldAccess( ImplicitThisFieldAccess access) { strictcount (access.getTarget()) = 1 and - strictcount (access.getEnclosingFunction()) = 1 and - not analyzableConst(access) + strictcount (access.getEnclosingFunction()) = 1 } private predicate mk_ImplicitThisFieldAccess( @@ -238,8 +251,7 @@ private predicate mk_ImplicitThisFieldAccess_with_deref( private predicate analyzableVariable(VariableAccess access) { not (access instanceof FieldAccess) and - strictcount (access.getTarget()) = 1 and - not analyzableConst(access) + strictcount (access.getTarget()) = 1 } private predicate mk_Variable(Variable x, VariableAccess access) { @@ -249,8 +261,7 @@ private predicate mk_Variable(Variable x, VariableAccess access) { private predicate analyzableConversion(Conversion conv) { strictcount (conv.getType().getUnspecifiedType()) = 1 and - strictcount (conv.getExpr()) = 1 and - not analyzableConst(conv) + strictcount (conv.getExpr()) = 1 } private predicate mk_Conversion(Type t, HC child, Conversion conv) { @@ -263,8 +274,7 @@ private predicate analyzableBinaryOp(BinaryOperation op) { op.isPure() and strictcount (op.getLeftOperand().getFullyConverted()) = 1 and strictcount (op.getRightOperand().getFullyConverted()) = 1 and - strictcount (op.getOperator()) = 1 and - not analyzableConst(op) + strictcount (op.getOperator()) = 1 } private predicate mk_BinaryOp( @@ -279,8 +289,7 @@ private predicate analyzableUnaryOp(UnaryOperation op) { not (op instanceof PointerDereferenceExpr) and op.isPure() and strictcount (op.getOperand().getFullyConverted()) = 1 and - strictcount (op.getOperator()) = 1 and - not analyzableConst(op) + strictcount (op.getOperator()) = 1 } private predicate mk_UnaryOp(HC child, string opname, UnaryOperation op) { @@ -290,8 +299,7 @@ private predicate mk_UnaryOp(HC child, string opname, UnaryOperation op) { } private predicate analyzableThisExpr(ThisExpr thisExpr) { - strictcount(thisExpr.getEnclosingFunction()) = 1 and - not analyzableConst(thisExpr) + strictcount(thisExpr.getEnclosingFunction()) = 1 } private predicate mk_ThisExpr(Function fcn, ThisExpr thisExpr) { @@ -301,8 +309,7 @@ private predicate mk_ThisExpr(Function fcn, ThisExpr thisExpr) { private predicate analyzableArrayAccess(ArrayExpr ae) { strictcount (ae.getArrayBase().getFullyConverted()) = 1 and - strictcount (ae.getArrayOffset().getFullyConverted()) = 1 and - not analyzableConst(ae) + strictcount (ae.getArrayOffset().getFullyConverted()) = 1 } private predicate mk_ArrayAccess( @@ -314,8 +321,7 @@ private predicate mk_ArrayAccess( private predicate analyzablePointerDereferenceExpr( PointerDereferenceExpr deref) { - strictcount (deref.getOperand().getFullyConverted()) = 1 and - not analyzableConst(deref) + strictcount (deref.getOperand().getFullyConverted()) = 1 } private predicate mk_Deref( @@ -327,12 +333,16 @@ private predicate mk_Deref( /** Gets the hash-cons of expression `e`. */ cached HC hashCons(Expr e) { exists (int val, Type t - | mk_IntConst(val, t, e) and - result = HC_IntConst(val, t)) + | mk_IntLiteral(val, t, e) and + result = HC_IntLiteral(val, t)) or exists (float val, Type t - | mk_FloatConst(val, t, e) and - result = HC_FloatConst(val, t)) + | mk_FloatLiteral(val, t, e) and + result = HC_FloatLiteral(val, t)) + or + exists (string val, Type t + | mk_StringLiteral(val, t, e) and + result = HC_StringLiteral(val, t)) or // Variable with no SSA information. exists (Variable x @@ -377,12 +387,6 @@ cached HC hashCons(Expr e) { or (not analyzableExpr(e,_) and result = HC_Unanalyzable(e)) } - -private predicate analyzableConst(Expr e) { - analyzableIntConst(e) or - analyzableFloatConst(e) -} - /** * Holds if the expression is explicitly handled by `hashCons`. * Unanalyzable expressions still need to be given a hash-cons, @@ -390,7 +394,9 @@ private predicate analyzableConst(Expr e) { * expression. */ predicate analyzableExpr(Expr e, string kind) { - (analyzableConst(e) and kind = "Const") or + (analyzableIntLiteral(e) and kind = "IntLiteral") or + (analyzableFloatLiteral(e) and kind = "FloatLiteral") or + (analyzableStringLiteral(e) and kind = "StringLiteral") or (analyzableDotFieldAccess(e) and kind = "DotFieldAccess") or (analyzablePointerFieldAccess(e) and kind = "PointerFieldAccess") or (analyzableImplicitThisFieldAccess(e) and kind = "ImplicitThisFieldAccess") or diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 8a350ba2f27..38f7b07c003 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -20,11 +20,12 @@ | test.cpp:43:7:43:24 | ... + ... | 43:c7-c24 45:c7-c24 | | test.cpp:43:12:43:13 | p1 | 43:c12-c13 45:c12-c13 | | test.cpp:43:17:43:24 | global03 | 43:c17-c24 45:c17-c24 | -| test.cpp:44:9:44:9 | 0 | 44:c9-c9 51:c25-c25 53:c18-c21 56:c39-c42 59:c17-c20 88:c12-c12 | +| test.cpp:44:9:44:9 | 0 | 44:c9-c9 51:c25-c25 88:c12-c12 | | test.cpp:53:10:53:13 | (int)... | 53:c10-c13 56:c21-c24 | | test.cpp:53:10:53:13 | * ... | 53:c10-c13 56:c21-c24 | | test.cpp:53:11:53:13 | str | 53:c11-c13 56:c22-c24 | | test.cpp:53:18:53:21 | 0 | 53:c18-c21 56:c39-c42 59:c17-c20 | +| test.cpp:53:18:53:21 | (int)... | 53:c18-c21 56:c39-c42 59:c17-c20 | | test.cpp:55:5:55:7 | ptr | 55:c5-c7 56:c14-c16 56:c32-c34 56:c47-c49 59:c10-c12 | | test.cpp:56:13:56:16 | (int)... | 56:c13-c16 56:c31-c34 59:c9-c12 | | test.cpp:56:13:56:16 | * ... | 56:c13-c16 56:c31-c34 59:c9-c12 | @@ -36,7 +37,7 @@ | test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | | test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 | -| test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 35:c16-c16 | +| test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | | test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | array to pointer conversion | 110:c15-c17 111:c9-c11 | From a8895f4bed89e2bc659ad5e02f4b6e2178aa1454 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 24 Aug 2018 11:38:59 -0700 Subject: [PATCH 13/73] C++: Support crement ops in HashCons --- cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll | 1 - .../library-tests/valuenumbering/HashCons/HashCons.expected | 1 + cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 9c468178ee9..2e3e88382ba 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -287,7 +287,6 @@ private predicate mk_BinaryOp( private predicate analyzableUnaryOp(UnaryOperation op) { not (op instanceof PointerDereferenceExpr) and - op.isPure() and strictcount (op.getOperand().getFullyConverted()) = 1 and strictcount (op.getOperator()) = 1 } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 38f7b07c003..ca55cb9adee 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -34,6 +34,7 @@ | test.cpp:79:11:79:14 | vals | 79:c11-c14 79:c24-c27 | | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | +| test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | | test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | | test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index d672f66e833..ecfa543d5c1 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -95,7 +95,7 @@ int regression_test00() { void test06(int x) { x++; - x++; // x is matched but x++ is not matched? + x++; // x++ is matched } // literals From b8bd285d640d307924fb90e41ef843e25a7d8b50 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 24 Aug 2018 16:35:00 -0700 Subject: [PATCH 14/73] C++: support functions in HashCons --- .../code/cpp/valuenumbering/HashCons.qll | 142 +++++++++++++++++- .../valuenumbering/HashCons/HashCons.expected | 12 ++ .../valuenumbering/HashCons/Uniqueness.ql | 2 +- .../valuenumbering/HashCons/test.cpp | 28 ++++ 4 files changed, 182 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 2e3e88382ba..d558f838136 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -80,10 +80,25 @@ private cached newtype HCBase = mk_ArrayAccess(x,i,_) } or + HC_NonmemberFunctionCall(Function fcn, HC_Args args) { + mk_NonmemberFunctionCall(fcn, args, _) + } + or + HC_MemberFunctionCall(Function trg, HC qual, HC_Args args) { + mk_MemberFunctionCall(trg, qual, args, _) + } + or // Any expression that is not handled by the cases above is // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } +private cached newtype HC_Args = + HC_EmptyArgs(Function fcn) { + any() + } + or HC_ArgCons(Function fcn, HC hc, int i, HC_Args list) { + mk_ArgCons(fcn, hc, i, list, _) + } /** * HC is the hash-cons of an expression. The relationship between `Expr` * and `HC` is many-to-one: every `Expr` has exactly one `HC`, but multiple @@ -120,6 +135,8 @@ class HC extends HCBase { if this instanceof HC_UnaryOp then result = "UnaryOp" else if this instanceof HC_ArrayAccess then result = "ArrayAccess" else if this instanceof HC_Unanalyzable then result = "Unanalyzable" else + if this instanceof HC_NonmemberFunctionCall then result = "NonmemberFunctionCall" else + if this instanceof HC_MemberFunctionCall then result = "MemberFunctionCall" else result = "error" } @@ -329,6 +346,116 @@ private predicate mk_Deref( p = hashCons(deref.getOperand().getFullyConverted()) } +private predicate analyzableNonmemberFunctionCall( + FunctionCall fc) { + forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i)) = 1) and + strictcount(fc.getTarget()) = 1 and + not fc.getTarget().isMember() +} + +private predicate mk_NonmemberFunctionCall( + Function fcn, + HC_Args args, + FunctionCall fc +) { + fc.getTarget() = fcn and + analyzableNonmemberFunctionCall(fc) and + ( + exists(HC head, HC_Args tail | + args = HC_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail) and + mk_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail, fc) + ) + or + fc.getNumberOfArguments() = 0 and + args = HC_EmptyArgs(fcn) + ) +} + +private predicate analyzableMemberFunctionCall( + FunctionCall fc) { + forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i)) = 1) and + strictcount(fc.getTarget()) = 1 and + strictcount(fc.getQualifier()) = 1 +} + +private predicate analyzableFunctionCall( + FunctionCall fc +) { + analyzableNonmemberFunctionCall(fc) + or + analyzableMemberFunctionCall(fc) +} + +private predicate mk_MemberFunctionCall( + Function fcn, + HC qual, + HC_Args args, + FunctionCall fc +) { + fc.getTarget() = fcn and + analyzableMemberFunctionCall(fc) and + hashCons(fc.getQualifier()) = qual and + ( + exists(HC head, HC_Args tail | + args = HC_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail) and + mk_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail, fc) + ) + or + fc.getNumberOfArguments() = 0 and + args = HC_EmptyArgs(fcn) + ) +} + +/* +private predicate analyzableImplicitThisFunctionCall(FunctionCall fc) { + forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i)) = 1) and + strictcount(fc.getTarget()) = 1 and + fc.getQualifier().(ThisExpr).isCompilerGenerated() and + fc.getTarget().isMember() +} + +private predicate mk_ImplicitThisFunctionCall(Function fcn, Function targ, HC_Args args, FunctionCall fc) { + analyzableImplicitThisFunctionCall(fc) and + fc.getTarget() = targ and + fc.getEnclosingFunction() = fcn and + analyzableImplicitThisFunctionCall(fc) and + ( + exists(HC head, HC_Args tail | + args = HC_ArgCons(targ, head, fc.getNumberOfArguments() - 1, tail) and + mk_ArgCons(targ, head, fc.getNumberOfArguments() - 1, tail, fc) + ) + or + fc.getNumberOfArguments() = 0 and + args = HC_EmptyArgs(targ) + ) +} + +private predicate mk_ImplicitThisFunctionCall_with_qualifier( + Function fcn, + Function targ, + HC qual, + HC_Args args, + FunctionCall fc) { + mk_ImplicitThisFunctionCall(fcn, targ, args, fc) and + qual = HC_ThisExpr(fcn) +} +*/ +private predicate mk_ArgCons(Function fcn, HC hc, int i, HC_Args list, FunctionCall fc) { + analyzableFunctionCall(fc) and + fc.getTarget() = fcn and + hc = hashCons(fc.getArgument(i).getFullyConverted()) and + ( + exists(HC head, HC_Args tail | + list = HC_ArgCons(fcn, head, i - 1, tail) and + mk_ArgCons(fcn, head, i - 1, tail, fc) and + i > 0 + ) + or + i = 0 and + list = HC_EmptyArgs(fcn) + ) +} + /** Gets the hash-cons of expression `e`. */ cached HC hashCons(Expr e) { exists (int val, Type t @@ -383,6 +510,17 @@ cached HC hashCons(Expr e) { exists (HC p | mk_Deref(p, e) and result = HC_Deref(p)) + or + exists(Function fcn, HC_Args args + | mk_NonmemberFunctionCall(fcn, args, e) and + result = HC_NonmemberFunctionCall(fcn, args) + ) + or + exists(Function fcn, HC qual, HC_Args args + | mk_MemberFunctionCall(fcn, qual, args, e) and + result = HC_MemberFunctionCall(fcn, qual, args) + ) + or (not analyzableExpr(e,_) and result = HC_Unanalyzable(e)) } @@ -405,5 +543,7 @@ predicate analyzableExpr(Expr e, string kind) { (analyzableUnaryOp(e) and kind = "UnaryOp") or (analyzableThisExpr(e) and kind = "ThisExpr") or (analyzableArrayAccess(e) and kind = "ArrayAccess") or - (analyzablePointerDereferenceExpr(e) and kind = "PointerDereferenceExpr") + (analyzablePointerDereferenceExpr(e) and kind = "PointerDereferenceExpr") or + (analyzableNonmemberFunctionCall(e) and kind = "NonmemberFunctionCall") or + (analyzableMemberFunctionCall(e) and kind = "MemberFunctionCall") } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index ca55cb9adee..621dc12a74f 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -1,3 +1,4 @@ +| file://:0:0:0:0 | this | 0:c0-c0 141:c23-c26 | | test.cpp:5:3:5:3 | x | 5:c3-c3 6:c3-c3 7:c7-c7 | | test.cpp:5:7:5:8 | p0 | 5:c7-c8 6:c7-c8 | | test.cpp:5:7:5:13 | ... + ... | 5:c7-c13 6:c7-c13 | @@ -30,6 +31,8 @@ | test.cpp:56:13:56:16 | (int)... | 56:c13-c16 56:c31-c34 59:c9-c12 | | test.cpp:56:13:56:16 | * ... | 56:c13-c16 56:c31-c34 59:c9-c12 | | test.cpp:62:5:62:10 | result | 62:c5-c10 65:c10-c15 | +| test.cpp:77:20:77:28 | call to getAValue | 77:c20-c28 80:c9-c17 | +| test.cpp:77:20:77:30 | (signed short)... | 77:c20-c30 80:c9-c19 | | test.cpp:79:7:79:7 | v | 79:c7-c7 80:c5-c5 | | test.cpp:79:11:79:14 | vals | 79:c11-c14 79:c24-c27 | | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | @@ -51,3 +54,12 @@ | test.cpp:116:3:116:3 | y | 116:c3-c3 117:c3-c3 118:c3-c3 | | test.cpp:117:7:117:9 | 0.10000000000000001 | 117:c7-c9 118:c7-c9 | | test.cpp:117:7:117:9 | (float)... | 117:c7-c9 118:c7-c9 | +| test.cpp:122:3:122:8 | call to test07 | 122:c3-c8 123:c3-c8 | +| test.cpp:125:3:125:11 | call to my_strspn | 125:c3-c11 126:c3-c11 | +| test.cpp:125:13:125:17 | array to pointer conversion | 125:c13-c17 126:c13-c17 129:c20-c24 | +| test.cpp:125:13:125:17 | foo | 125:c13-c17 126:c13-c17 129:c20-c24 | +| test.cpp:125:20:125:24 | array to pointer conversion | 125:c20-c24 126:c20-c24 129:c13-c17 | +| test.cpp:125:20:125:24 | bar | 125:c20-c24 126:c20-c24 129:c13-c17 | +| test.cpp:141:12:141:17 | call to getInt | 141:c12-c17 141:c29-c34 | +| test.cpp:146:10:146:11 | ih | 146:c10-c11 146:c31-c32 | +| test.cpp:146:13:146:25 | call to getDoubledInt | 146:c13-c25 146:c34-c46 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql b/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql index f0ee6d237d3..084c2b1b081 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql @@ -1,7 +1,7 @@ import cpp import semmle.code.cpp.valuenumbering.HashCons -// Every expression should have exactly one GVN. +// Every expression should have exactly one HC. // So this query should have zero results. from Expr e where count(hashCons(e)) != 1 diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index ecfa543d5c1..36e31444af2 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -117,3 +117,31 @@ void test07() { y = 0.1; y = 0.1; } + +void test08() { + test07(); + test07(); + + my_strspn("foo", "bar"); + my_strspn("foo", "bar"); + + + my_strspn("bar", "foo"); +} + +class IntHolder { + int myInt; + + int getInt() { + return myInt; + } + +public: + int getDoubledInt() { + return getInt() + this->getInt(); + } +}; + +int quadrupleInt(IntHolder ih) { + return ih.getDoubledInt() + ih.getDoubledInt(); +} From 77c5a8e7bfc7bc93f93975e422421fc05847016e Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 24 Aug 2018 17:15:28 -0700 Subject: [PATCH 15/73] C++: support impure binary operations in HashCons --- cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll | 1 - .../valuenumbering/HashCons/HashCons.expected | 3 +++ .../library-tests/valuenumbering/HashCons/test.cpp | 10 ++++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index d558f838136..5d8efccea28 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -288,7 +288,6 @@ private predicate mk_Conversion(Type t, HC child, Conversion conv) { } private predicate analyzableBinaryOp(BinaryOperation op) { - op.isPure() and strictcount (op.getLeftOperand().getFullyConverted()) = 1 and strictcount (op.getRightOperand().getFullyConverted()) = 1 and strictcount (op.getOperator()) = 1 diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 621dc12a74f..3998c67bbac 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -63,3 +63,6 @@ | test.cpp:141:12:141:17 | call to getInt | 141:c12-c17 141:c29-c34 | | test.cpp:146:10:146:11 | ih | 146:c10-c11 146:c31-c32 | | test.cpp:146:13:146:25 | call to getDoubledInt | 146:c13-c25 146:c34-c46 | +| test.cpp:150:3:150:3 | x | 150:c3-c3 150:c9-c9 151:c3-c3 151:c9-c9 152:c12-c12 | +| test.cpp:150:3:150:5 | ... ++ | 150:c3-c5 150:c9-c11 151:c3-c5 151:c9-c11 152:c10-c12 | +| test.cpp:150:3:150:11 | ... + ... | 150:c3-c11 151:c3-c11 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 36e31444af2..20e249d72a4 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -138,10 +138,16 @@ class IntHolder { public: int getDoubledInt() { - return getInt() + this->getInt(); + return getInt() + this->getInt(); // getInt() and this->getInt() should be the same } }; -int quadrupleInt(IntHolder ih) { +int test09(IntHolder ih) { return ih.getDoubledInt() + ih.getDoubledInt(); } + +int test10(int x) { + x++ + x++; + x++ + x++; // same as above + return ++x; // ++x is not the same as x++ +} From e0af30a789027f94609bf558dd29a6f6e23879d6 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 24 Aug 2018 17:42:30 -0700 Subject: [PATCH 16/73] C++: clean up commented-out code --- .../code/cpp/valuenumbering/HashCons.qll | 51 ++++--------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 5d8efccea28..f6eb700ce5f 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -92,6 +92,7 @@ private cached newtype HCBase = // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } +/** Used to implement hash-consing of argument lists */ private cached newtype HC_Args = HC_EmptyArgs(Function fcn) { any() @@ -377,14 +378,6 @@ private predicate analyzableMemberFunctionCall( strictcount(fc.getQualifier()) = 1 } -private predicate analyzableFunctionCall( - FunctionCall fc -) { - analyzableNonmemberFunctionCall(fc) - or - analyzableMemberFunctionCall(fc) -} - private predicate mk_MemberFunctionCall( Function fcn, HC qual, @@ -405,40 +398,18 @@ private predicate mk_MemberFunctionCall( ) } -/* -private predicate analyzableImplicitThisFunctionCall(FunctionCall fc) { - forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i)) = 1) and - strictcount(fc.getTarget()) = 1 and - fc.getQualifier().(ThisExpr).isCompilerGenerated() and - fc.getTarget().isMember() +private predicate analyzableFunctionCall( + FunctionCall fc +) { + analyzableNonmemberFunctionCall(fc) + or + analyzableMemberFunctionCall(fc) } -private predicate mk_ImplicitThisFunctionCall(Function fcn, Function targ, HC_Args args, FunctionCall fc) { - analyzableImplicitThisFunctionCall(fc) and - fc.getTarget() = targ and - fc.getEnclosingFunction() = fcn and - analyzableImplicitThisFunctionCall(fc) and - ( - exists(HC head, HC_Args tail | - args = HC_ArgCons(targ, head, fc.getNumberOfArguments() - 1, tail) and - mk_ArgCons(targ, head, fc.getNumberOfArguments() - 1, tail, fc) - ) - or - fc.getNumberOfArguments() = 0 and - args = HC_EmptyArgs(targ) - ) -} - -private predicate mk_ImplicitThisFunctionCall_with_qualifier( - Function fcn, - Function targ, - HC qual, - HC_Args args, - FunctionCall fc) { - mk_ImplicitThisFunctionCall(fcn, targ, args, fc) and - qual = HC_ThisExpr(fcn) -} -*/ +/** + * Holds if `fc` is a call to `fcn`, `fc`'s first `i-1` arguments have hash-cons + * `list`, and `fc`'s `i`th argument has hash-cons `hc` + */ private predicate mk_ArgCons(Function fcn, HC hc, int i, HC_Args list, FunctionCall fc) { analyzableFunctionCall(fc) and fc.getTarget() = fcn and From 3a5eb03055eaea3295703421f7250cb3ebdb3ec8 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 27 Aug 2018 10:05:09 -0700 Subject: [PATCH 17/73] C++: change floating point value in test --- .../library-tests/valuenumbering/HashCons/HashCons.expected | 2 +- cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 3998c67bbac..6cc1b8589e5 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -52,7 +52,7 @@ | test.cpp:115:13:115:15 | 0.0 | 115:c13-c15 116:c7-c9 | | test.cpp:115:13:115:15 | (float)... | 115:c13-c15 116:c7-c9 | | test.cpp:116:3:116:3 | y | 116:c3-c3 117:c3-c3 118:c3-c3 | -| test.cpp:117:7:117:9 | 0.10000000000000001 | 117:c7-c9 118:c7-c9 | +| test.cpp:117:7:117:9 | 0.5 | 117:c7-c9 118:c7-c9 | | test.cpp:117:7:117:9 | (float)... | 117:c7-c9 118:c7-c9 | | test.cpp:122:3:122:8 | call to test07 | 122:c3-c8 123:c3-c8 | | test.cpp:125:3:125:11 | call to my_strspn | 125:c3-c11 126:c3-c11 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 20e249d72a4..f073654c38b 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -114,8 +114,8 @@ void test07() { float y = 0.0; y = 0.0; - y = 0.1; - y = 0.1; + y = 0.5; + y = 0.5; } void test08() { From 91da02bacfc2a02ead34ba5e2a64f434049ac309 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 27 Aug 2018 11:38:16 -0700 Subject: [PATCH 18/73] C++: uniqueness fixes for HashCons --- .../code/cpp/valuenumbering/HashCons.qll | 44 ++++++++++++++----- .../valuenumbering/HashCons/HashCons.expected | 1 + .../valuenumbering/HashCons/test.cpp | 5 +++ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index f6eb700ce5f..fb366b079fa 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -47,6 +47,8 @@ private cached newtype HCBase = or HC_StringLiteral(string val, Type t) {mk_StringLiteral(val,t,_)} or + HC_Nullptr() {mk_Nullptr(_)} + or HC_Variable(Variable x) { mk_Variable(x, _) } @@ -167,30 +169,42 @@ class HC extends HCBase { private predicate analyzableIntLiteral(Literal e) { strictcount (e.getValue().toInt()) = 1 and - strictcount (e.getType().getUnspecifiedType()) = 1 + strictcount (e.getType().getUnspecifiedType()) = 1 and + e.getType().getUnspecifiedType() instanceof IntegralType } private predicate mk_IntLiteral(int val, Type t, Expr e) { analyzableIntLiteral(e) and val = e.getValue().toInt() and - t = e.getType().getUnspecifiedType() and - t instanceof IntegralType + t = e.getType().getUnspecifiedType() } + + private predicate analyzableFloatLiteral(Literal e) { strictcount (e.getValue().toFloat()) = 1 and - strictcount (e.getType().getUnspecifiedType()) = 1 + strictcount (e.getType().getUnspecifiedType()) = 1 and + e.getType().getUnspecifiedType() instanceof FloatingPointType } private predicate mk_FloatLiteral(float val, Type t, Expr e) { analyzableFloatLiteral(e) and val = e.getValue().toFloat() and - t = e.getType().getUnspecifiedType() and - t instanceof FloatingPointType + t = e.getType().getUnspecifiedType() +} + +private predicate analyzableNullptr(NullValue e) { + strictcount (e.getType().getUnspecifiedType()) = 1 and + e.getType() instanceof NullPointerType +} + +private predicate mk_Nullptr(Expr e) { + analyzableNullptr(e) } private predicate analyzableStringLiteral(Literal e) { strictcount(e.getValue()) = 1 and - strictcount(e.getType().getUnspecifiedType()) = 1 + strictcount(e.getType().getUnspecifiedType()) = 1 and + e.getType().getUnspecifiedType().(ArrayType).getBaseType() instanceof CharType } private predicate mk_StringLiteral(string val, Type t, Expr e) { @@ -348,9 +362,9 @@ private predicate mk_Deref( private predicate analyzableNonmemberFunctionCall( FunctionCall fc) { - forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i)) = 1) and + forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i).getFullyConverted()) = 1) and strictcount(fc.getTarget()) = 1 and - not fc.getTarget().isMember() + not exists(fc.getQualifier()) } private predicate mk_NonmemberFunctionCall( @@ -373,9 +387,9 @@ private predicate mk_NonmemberFunctionCall( private predicate analyzableMemberFunctionCall( FunctionCall fc) { - forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i)) = 1) and + forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i).getFullyConverted()) = 1) and strictcount(fc.getTarget()) = 1 and - strictcount(fc.getQualifier()) = 1 + strictcount(fc.getQualifier().getFullyConverted()) = 1 } private predicate mk_MemberFunctionCall( @@ -386,7 +400,7 @@ private predicate mk_MemberFunctionCall( ) { fc.getTarget() = fcn and analyzableMemberFunctionCall(fc) and - hashCons(fc.getQualifier()) = qual and + hashCons(fc.getQualifier().getFullyConverted()) = qual and ( exists(HC head, HC_Args tail | args = HC_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail) and @@ -490,6 +504,11 @@ cached HC hashCons(Expr e) { | mk_MemberFunctionCall(fcn, qual, args, e) and result = HC_MemberFunctionCall(fcn, qual, args) ) + or + ( + mk_Nullptr(e) and + result = HC_Nullptr() + ) or (not analyzableExpr(e,_) and result = HC_Unanalyzable(e)) @@ -504,6 +523,7 @@ predicate analyzableExpr(Expr e, string kind) { (analyzableIntLiteral(e) and kind = "IntLiteral") or (analyzableFloatLiteral(e) and kind = "FloatLiteral") or (analyzableStringLiteral(e) and kind = "StringLiteral") or + (analyzableNullptr(e) and kind = "Nullptr") or (analyzableDotFieldAccess(e) and kind = "DotFieldAccess") or (analyzablePointerFieldAccess(e) and kind = "PointerFieldAccess") or (analyzableImplicitThisFieldAccess(e) and kind = "ImplicitThisFieldAccess") or diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 6cc1b8589e5..f921b24dc1c 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -66,3 +66,4 @@ | test.cpp:150:3:150:3 | x | 150:c3-c3 150:c9-c9 151:c3-c3 151:c9-c9 152:c12-c12 | | test.cpp:150:3:150:5 | ... ++ | 150:c3-c5 150:c9-c11 151:c3-c5 151:c9-c11 152:c10-c12 | | test.cpp:150:3:150:11 | ... + ... | 150:c3-c11 151:c3-c11 | +| test.cpp:156:14:156:20 | 0 | 156:c14-c20 156:c3-c9 157:c10-c16 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index f073654c38b..89661e2b6d7 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -151,3 +151,8 @@ int test10(int x) { x++ + x++; // same as above return ++x; // ++x is not the same as x++ } + +void* test11() { + nullptr == nullptr; + return nullptr; +} From e6314c5f35b947c1908a8ca690cc0d6822000bd7 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 27 Aug 2018 11:48:37 -0700 Subject: [PATCH 19/73] C++: add support for enums in HashCons --- .../code/cpp/valuenumbering/HashCons.qll | 18 ++++++++++++++++++ .../valuenumbering/HashCons/HashCons.expected | 2 ++ .../valuenumbering/HashCons/test.cpp | 16 ++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index fb366b079fa..0a8fd0c0c1f 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -43,6 +43,8 @@ import cpp private cached newtype HCBase = HC_IntLiteral(int val, Type t) { mk_IntLiteral(val,t,_) } or + HC_EnumConstantAccess(EnumConstant val, Type t) { mk_EnumConstantAccess(val,t,_) } + or HC_FloatLiteral(float val, Type t) { mk_FloatLiteral(val,t,_) } or HC_StringLiteral(string val, Type t) {mk_StringLiteral(val,t,_)} @@ -179,6 +181,17 @@ private predicate mk_IntLiteral(int val, Type t, Expr e) { t = e.getType().getUnspecifiedType() } +private predicate analyzableEnumConstantAccess(EnumConstantAccess e) { + strictcount (e.getValue().toInt()) = 1 and + strictcount (e.getType().getUnspecifiedType()) = 1 and + e.getType().getUnspecifiedType() instanceof Enum +} + +private predicate mk_EnumConstantAccess(EnumConstant val, Type t, Expr e) { + analyzableEnumConstantAccess(e) and + val = e.(EnumConstantAccess).getTarget() and + t = e.getType().getUnspecifiedType() +} private predicate analyzableFloatLiteral(Literal e) { strictcount (e.getValue().toFloat()) = 1 and @@ -446,6 +459,10 @@ cached HC hashCons(Expr e) { | mk_IntLiteral(val, t, e) and result = HC_IntLiteral(val, t)) or + exists (EnumConstant val, Type t + | mk_EnumConstantAccess(val, t, e) and + result = HC_EnumConstantAccess(val, t)) + or exists (float val, Type t | mk_FloatLiteral(val, t, e) and result = HC_FloatLiteral(val, t)) @@ -521,6 +538,7 @@ cached HC hashCons(Expr e) { */ predicate analyzableExpr(Expr e, string kind) { (analyzableIntLiteral(e) and kind = "IntLiteral") or + (analyzableEnumConstantAccess(e) and kind = "EnumConstantAccess") or (analyzableFloatLiteral(e) and kind = "FloatLiteral") or (analyzableStringLiteral(e) and kind = "StringLiteral") or (analyzableNullptr(e) and kind = "Nullptr") or diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index f921b24dc1c..d1a42db322d 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -67,3 +67,5 @@ | test.cpp:150:3:150:5 | ... ++ | 150:c3-c5 150:c9-c11 151:c3-c5 151:c9-c11 152:c10-c12 | | test.cpp:150:3:150:11 | ... + ... | 150:c3-c11 151:c3-c11 | | test.cpp:156:14:156:20 | 0 | 156:c14-c20 156:c3-c9 157:c10-c16 | +| test.cpp:171:3:171:6 | (int)... | 171:c3-c6 172:c3-c6 | +| test.cpp:171:3:171:6 | e1x1 | 171:c3-c6 172:c3-c6 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 89661e2b6d7..e8fff46a5c9 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -156,3 +156,19 @@ void* test11() { nullptr == nullptr; return nullptr; } + +enum t1 { + e1x1 = 1, + e1x2 = 2 +}; + +enum t2 { + e2x1 = 1, + e2x2 = 2 +}; + +int test12() { + e1x1 == e2x1; + e1x1 == e2x2; + return e1x2; +} From fede8d63d4fed29924d79c0744a3c5e138ee4e7c Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 27 Aug 2018 13:44:27 -0700 Subject: [PATCH 20/73] C++: respond to PR comments --- .../code/cpp/valuenumbering/HashCons.qll | 118 ++++++++---------- .../valuenumbering/HashCons/HashCons.expected | 6 +- .../valuenumbering/HashCons/HashCons.ql | 2 +- .../valuenumbering/HashCons/Uniqueness.ql | 2 +- .../valuenumbering/HashCons/test.cpp | 12 ++ 5 files changed, 74 insertions(+), 66 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 0a8fd0c0c1f..3fe9242086c 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -55,13 +55,13 @@ private cached newtype HCBase = mk_Variable(x, _) } or - HC_FieldAccess(HC s, Field f) { + HC_FieldAccess(HashCons s, Field f) { mk_DotFieldAccess(s,f,_) or mk_PointerFieldAccess_with_deref(s,f,_) or mk_ImplicitThisFieldAccess_with_deref(s,f,_) } or - HC_Deref(HC p) { + HC_Deref(HashCons p) { mk_Deref(p,_) or mk_PointerFieldAccess(p,_,_) or mk_ImplicitThisFieldAccess_with_qualifier(p,_,_) @@ -72,15 +72,15 @@ private cached newtype HCBase = mk_ImplicitThisFieldAccess(fcn,_,_) } or - HC_Conversion(Type t, HC child) { mk_Conversion(t, child, _) } + HC_Conversion(Type t, HashCons child) { mk_Conversion(t, child, _) } or - HC_BinaryOp(HC lhs, HC rhs, string opname) { + HC_BinaryOp(HashCons lhs, HashCons rhs, string opname) { mk_BinaryOp(lhs, rhs, opname, _) } or - HC_UnaryOp(HC child, string opname) { mk_UnaryOp(child, opname, _) } + HC_UnaryOp(HashCons child, string opname) { mk_UnaryOp(child, opname, _) } or - HC_ArrayAccess(HC x, HC i) { + HC_ArrayAccess(HashCons x, HashCons i) { mk_ArrayAccess(x,i,_) } or @@ -88,7 +88,7 @@ private cached newtype HCBase = mk_NonmemberFunctionCall(fcn, args, _) } or - HC_MemberFunctionCall(Function trg, HC qual, HC_Args args) { + HC_MemberFunctionCall(Function trg, HashCons qual, HC_Args args) { mk_MemberFunctionCall(trg, qual, args, _) } or @@ -101,11 +101,11 @@ private cached newtype HC_Args = HC_EmptyArgs(Function fcn) { any() } - or HC_ArgCons(Function fcn, HC hc, int i, HC_Args list) { + or HC_ArgCons(Function fcn, HashCons hc, int i, HC_Args list) { mk_ArgCons(fcn, hc, i, list, _) } /** - * HC is the hash-cons of an expression. The relationship between `Expr` + * HashCons is the hash-cons of an expression. The relationship between `Expr` * and `HC` is many-to-one: every `Expr` has exactly one `HC`, but multiple * expressions can have the same `HC`. If two expressions have the same * `HC`, it means that they are structurally equal. The `HC` is an opaque @@ -118,9 +118,7 @@ private cached newtype HC_Args = * expression with this `HC` and using its `toString` and `getLocation` * methods. */ -class HC extends HCBase { - HC() { this instanceof HCBase } - +class HashCons extends HCBase { /** Gets an expression that has this HC. */ Expr getAnExpr() { this = hashCons(result) @@ -234,7 +232,7 @@ private predicate analyzableDotFieldAccess(DotFieldAccess access) { } private predicate mk_DotFieldAccess( - HC qualifier, Field target, DotFieldAccess access) { + HashCons qualifier, Field target, DotFieldAccess access) { analyzableDotFieldAccess(access) and target = access.getTarget() and qualifier = hashCons(access.getQualifier().getFullyConverted()) @@ -246,9 +244,9 @@ private predicate analyzablePointerFieldAccess(PointerFieldAccess access) { } private predicate mk_PointerFieldAccess( - HC qualifier, Field target, + HashCons qualifier, Field target, PointerFieldAccess access) { - analyzablePointerFieldAccess(access) and + analyzablePointerFieldAccess(access) and target = access.getTarget() and qualifier = hashCons(access.getQualifier().getFullyConverted()) } @@ -257,38 +255,35 @@ private predicate mk_PointerFieldAccess( * `obj->field` is equivalent to `(*obj).field`, so we need to wrap an * extra `HC_Deref` around the qualifier. */ -private predicate mk_PointerFieldAccess_with_deref( - HC new_qualifier, Field target, PointerFieldAccess access) { - exists (HC qualifier +private predicate mk_PointerFieldAccess_with_deref (HashCons new_qualifier, Field target, + PointerFieldAccess access) { + exists (HashCons qualifier | mk_PointerFieldAccess(qualifier, target, access) and new_qualifier = HC_Deref(qualifier)) } -private predicate analyzableImplicitThisFieldAccess( - ImplicitThisFieldAccess access) { +private predicate analyzableImplicitThisFieldAccess(ImplicitThisFieldAccess access) { strictcount (access.getTarget()) = 1 and strictcount (access.getEnclosingFunction()) = 1 } -private predicate mk_ImplicitThisFieldAccess( - Function fcn, Field target, +private predicate mk_ImplicitThisFieldAccess(Function fcn, Field target, ImplicitThisFieldAccess access) { analyzableImplicitThisFieldAccess(access) and target = access.getTarget() and fcn = access.getEnclosingFunction() } -private predicate mk_ImplicitThisFieldAccess_with_qualifier( - HC qualifier, Field target, +private predicate mk_ImplicitThisFieldAccess_with_qualifier( HashCons qualifier, Field target, ImplicitThisFieldAccess access) { exists (Function fcn | mk_ImplicitThisFieldAccess(fcn, target, access) and qualifier = HC_ThisExpr(fcn)) } -private predicate mk_ImplicitThisFieldAccess_with_deref( - HC new_qualifier, Field target, ImplicitThisFieldAccess access) { - exists (HC qualifier +private predicate mk_ImplicitThisFieldAccess_with_deref(HashCons new_qualifier, Field target, + ImplicitThisFieldAccess access) { + exists (HashCons qualifier | mk_ImplicitThisFieldAccess_with_qualifier( qualifier, target, access) and new_qualifier = HC_Deref(qualifier)) @@ -309,7 +304,7 @@ private predicate analyzableConversion(Conversion conv) { strictcount (conv.getExpr()) = 1 } -private predicate mk_Conversion(Type t, HC child, Conversion conv) { +private predicate mk_Conversion(Type t, HashCons child, Conversion conv) { analyzableConversion(conv) and t = conv.getType().getUnspecifiedType() and child = hashCons(conv.getExpr()) @@ -321,8 +316,7 @@ private predicate analyzableBinaryOp(BinaryOperation op) { strictcount (op.getOperator()) = 1 } -private predicate mk_BinaryOp( - HC lhs, HC rhs, string opname, BinaryOperation op) { +private predicate mk_BinaryOp(HashCons lhs, HashCons rhs, string opname, BinaryOperation op) { analyzableBinaryOp(op) and lhs = hashCons(op.getLeftOperand().getFullyConverted()) and rhs = hashCons(op.getRightOperand().getFullyConverted()) and @@ -335,7 +329,7 @@ private predicate analyzableUnaryOp(UnaryOperation op) { strictcount (op.getOperator()) = 1 } -private predicate mk_UnaryOp(HC child, string opname, UnaryOperation op) { +private predicate mk_UnaryOp(HashCons child, string opname, UnaryOperation op) { analyzableUnaryOp(op) and child = hashCons(op.getOperand().getFullyConverted()) and opname = op.getOperator() @@ -355,40 +349,36 @@ private predicate analyzableArrayAccess(ArrayExpr ae) { strictcount (ae.getArrayOffset().getFullyConverted()) = 1 } -private predicate mk_ArrayAccess( - HC base, HC offset, ArrayExpr ae) { +private predicate mk_ArrayAccess(HashCons base, HashCons offset, ArrayExpr ae) { analyzableArrayAccess(ae) and base = hashCons(ae.getArrayBase().getFullyConverted()) and offset = hashCons(ae.getArrayOffset().getFullyConverted()) } -private predicate analyzablePointerDereferenceExpr( - PointerDereferenceExpr deref) { +private predicate analyzablePointerDereferenceExpr(PointerDereferenceExpr deref) { strictcount (deref.getOperand().getFullyConverted()) = 1 } -private predicate mk_Deref( - HC p, PointerDereferenceExpr deref) { +private predicate mk_Deref(HashCons p, PointerDereferenceExpr deref) { analyzablePointerDereferenceExpr(deref) and p = hashCons(deref.getOperand().getFullyConverted()) } -private predicate analyzableNonmemberFunctionCall( - FunctionCall fc) { - forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i).getFullyConverted()) = 1) and +private predicate analyzableNonmemberFunctionCall(FunctionCall fc) { + forall(int i | + exists(fc.getArgument(i)) | + strictcount(fc.getArgument(i).getFullyConverted()) = 1 + ) and strictcount(fc.getTarget()) = 1 and not exists(fc.getQualifier()) } -private predicate mk_NonmemberFunctionCall( - Function fcn, - HC_Args args, - FunctionCall fc +private predicate mk_NonmemberFunctionCall(Function fcn, HC_Args args, FunctionCall fc ) { fc.getTarget() = fcn and analyzableNonmemberFunctionCall(fc) and ( - exists(HC head, HC_Args tail | + exists(HashCons head, HC_Args tail | args = HC_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail) and mk_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail, fc) ) @@ -400,14 +390,17 @@ private predicate mk_NonmemberFunctionCall( private predicate analyzableMemberFunctionCall( FunctionCall fc) { - forall(int i | exists(fc.getArgument(i)) | strictcount(fc.getArgument(i).getFullyConverted()) = 1) and + forall(int i | + exists(fc.getArgument(i)) | + strictcount(fc.getArgument(i).getFullyConverted()) = 1 + ) and strictcount(fc.getTarget()) = 1 and strictcount(fc.getQualifier().getFullyConverted()) = 1 } private predicate mk_MemberFunctionCall( Function fcn, - HC qual, + HashCons qual, HC_Args args, FunctionCall fc ) { @@ -415,7 +408,7 @@ private predicate mk_MemberFunctionCall( analyzableMemberFunctionCall(fc) and hashCons(fc.getQualifier().getFullyConverted()) = qual and ( - exists(HC head, HC_Args tail | + exists(HashCons head, HC_Args tail | args = HC_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail) and mk_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail, fc) ) @@ -434,15 +427,15 @@ private predicate analyzableFunctionCall( } /** - * Holds if `fc` is a call to `fcn`, `fc`'s first `i-1` arguments have hash-cons - * `list`, and `fc`'s `i`th argument has hash-cons `hc` + * Holds if `fc` is a call to `fcn`, `fc`'s first `i` arguments have hash-cons + * `list`, and `fc`'s argument at index `i` has hash-cons `hc`. */ -private predicate mk_ArgCons(Function fcn, HC hc, int i, HC_Args list, FunctionCall fc) { +private predicate mk_ArgCons(Function fcn, HashCons hc, int i, HC_Args list, FunctionCall fc) { analyzableFunctionCall(fc) and fc.getTarget() = fcn and hc = hashCons(fc.getArgument(i).getFullyConverted()) and ( - exists(HC head, HC_Args tail | + exists(HashCons head, HC_Args tail | list = HC_ArgCons(fcn, head, i - 1, tail) and mk_ArgCons(fcn, head, i - 1, tail, fc) and i > 0 @@ -454,7 +447,7 @@ private predicate mk_ArgCons(Function fcn, HC hc, int i, HC_Args list, FunctionC } /** Gets the hash-cons of expression `e`. */ -cached HC hashCons(Expr e) { +cached HashCons hashCons(Expr e) { exists (int val, Type t | mk_IntLiteral(val, t, e) and result = HC_IntLiteral(val, t)) @@ -471,20 +464,19 @@ cached HC hashCons(Expr e) { | mk_StringLiteral(val, t, e) and result = HC_StringLiteral(val, t)) or - // Variable with no SSA information. exists (Variable x | mk_Variable(x, e) and result = HC_Variable(x)) or - exists (HC qualifier, Field target + exists (HashCons qualifier, Field target | mk_DotFieldAccess(qualifier, target, e) and result = HC_FieldAccess(qualifier, target)) or - exists (HC qualifier, Field target + exists (HashCons qualifier, Field target | mk_PointerFieldAccess_with_deref(qualifier, target, e) and result = HC_FieldAccess(qualifier, target)) or - exists (HC qualifier, Field target + exists (HashCons qualifier, Field target | mk_ImplicitThisFieldAccess_with_deref(qualifier, target, e) and result = HC_FieldAccess(qualifier, target)) or @@ -492,23 +484,23 @@ cached HC hashCons(Expr e) { | mk_ThisExpr(fcn, e) and result = HC_ThisExpr(fcn)) or - exists (Type t, HC child + exists (Type t, HashCons child | mk_Conversion(t, child, e) and result = HC_Conversion(t, child)) or - exists (HC lhs, HC rhs, string opname + exists (HashCons lhs, HashCons rhs, string opname | mk_BinaryOp(lhs, rhs, opname, e) and result = HC_BinaryOp(lhs, rhs, opname)) or - exists (HC child, string opname + exists (HashCons child, string opname | mk_UnaryOp(child, opname, e) and result = HC_UnaryOp(child, opname)) or - exists (HC x, HC i + exists (HashCons x, HashCons i | mk_ArrayAccess(x, i, e) and result = HC_ArrayAccess(x, i)) or - exists (HC p + exists (HashCons p | mk_Deref(p, e) and result = HC_Deref(p)) or @@ -517,7 +509,7 @@ cached HC hashCons(Expr e) { result = HC_NonmemberFunctionCall(fcn, args) ) or - exists(Function fcn, HC qual, HC_Args args + exists(Function fcn, HashCons qual, HC_Args args | mk_MemberFunctionCall(fcn, qual, args, e) and result = HC_MemberFunctionCall(fcn, qual, args) ) diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index d1a42db322d..7f4a5d6ed38 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -38,7 +38,7 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | | test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | @@ -69,3 +69,7 @@ | test.cpp:156:14:156:20 | 0 | 156:c14-c20 156:c3-c9 157:c10-c16 | | test.cpp:171:3:171:6 | (int)... | 171:c3-c6 172:c3-c6 | | test.cpp:171:3:171:6 | e1x1 | 171:c3-c6 172:c3-c6 | +| test.cpp:179:10:179:22 | (...) | 179:c10-c22 179:c10-c22 | +| test.cpp:179:17:179:17 | y | 179:c17-c17 179:c17-c17 | +| test.cpp:179:17:179:21 | ... + ... | 179:c17-c21 179:c17-c21 | +| test.cpp:185:17:185:17 | y | 185:c17-c17 185:c17-c17 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.ql b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.ql index edd5ae2c63b..577e6ac62de 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.ql +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.ql @@ -1,7 +1,7 @@ import cpp import semmle.code.cpp.valuenumbering.HashCons -from HC h +from HashCons h where strictcount(h.getAnExpr()) > 1 select h, diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql b/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql index 084c2b1b081..d953b53cd49 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/Uniqueness.ql @@ -5,4 +5,4 @@ import semmle.code.cpp.valuenumbering.HashCons // So this query should have zero results. from Expr e where count(hashCons(e)) != 1 -select e, concat(HC h | h = hashCons(e) | h.getKind(), ", ") +select e, concat(HashCons h | h = hashCons(e) | h.getKind(), ", ") diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index e8fff46a5c9..ed31f21d35a 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -172,3 +172,15 @@ int test12() { e1x1 == e2x2; return e1x2; } + +#define SQUARE(x) ((x) * (x)) + +int test13(int y) { + return SQUARE(y + 1); +} + +#define SQUARE(x) x * x + +int test14(int y) { + return SQUARE(y); +} From 5549b6fcab04119f4b436b4f0c051478bb4253cf Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 28 Aug 2018 13:28:11 -0700 Subject: [PATCH 21/73] C++: HashCons for new, new[], sizeof, alignof --- .../code/cpp/valuenumbering/HashCons.qll | 274 +++++++++++++++++- .../valuenumbering/HashCons/HashCons.expected | 28 +- .../valuenumbering/HashCons/test.cpp | 87 ++++++ 3 files changed, 383 insertions(+), 6 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 3fe9242086c..fedaabf47e5 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -2,9 +2,9 @@ * Provides an implementation of Hash consing. * See https://en.wikipedia.org/wiki/Hash_consing * - * The predicate `hashCons` converts an expression into a `HC`, which is an + * The predicate `hashCons` converts an expression into a `HashCons`, which is an * abstract type presenting the hash-cons of the expression. If two - * expressions have the same `HC` then they are structurally equal. + * expressions have the same `HashCons` then they are structurally equal. * * Important note: this library ignores the possibility that the value of * an expression might change between one occurrence and the next. For @@ -92,18 +92,51 @@ private cached newtype HCBase = mk_MemberFunctionCall(trg, qual, args, _) } or + HC_NewExpr(Type t, HC_Alloc alloc, HC_Init init) { + mk_NewExpr(t, alloc, init, _, _) + } or + HC_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init) { + mk_NewArrayExpr(t, alloc, init, _, _) + } + or + HC_SizeofType(Type t) {mk_SizeofType(t, _)} + or + HC_SizeofExpr(HashCons child) {mk_SizeofExpr(child, _)} + or + HC_AlignofType(Type t) {mk_AlignofType(t, _)} + or + HC_AlignofExpr(HashCons child) {mk_AlignofExpr(child, _)} + or // Any expression that is not handled by the cases above is // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } +/** Used to implement hash-consing of `new` placement argument lists */ +private newtype HC_Alloc = + HC_EmptyAllocArgs(Function fcn) { + exists(NewOrNewArrayExpr n | + n.getAllocator() = fcn + ) + } + or HC_AllocArgCons(Function fcn, HashCons hc, int i, HC_Alloc list, boolean aligned) { + mk_AllocArgCons(fcn, hc, i, list, aligned, _) + } + or + HC_NoAlloc() +private newtype HC_Init = + HC_NoInit() + or + HC_HasInit(HashCons hc) {mk_HasInit(hc, _)} + /** Used to implement hash-consing of argument lists */ -private cached newtype HC_Args = +private newtype HC_Args = HC_EmptyArgs(Function fcn) { any() } or HC_ArgCons(Function fcn, HashCons hc, int i, HC_Args list) { mk_ArgCons(fcn, hc, i, list, _) } + /** * HashCons is the hash-cons of an expression. The relationship between `Expr` * and `HC` is many-to-one: every `Expr` has exactly one `HC`, but multiple @@ -127,8 +160,10 @@ class HashCons extends HCBase { /** Gets the kind of the HC. This can be useful for debugging. */ string getKind() { if this instanceof HC_IntLiteral then result = "IntLiteral" else + if this instanceof HC_EnumConstantAccess then result = "EnumConstantAccess" else if this instanceof HC_FloatLiteral then result = "FloatLiteral" else if this instanceof HC_StringLiteral then result = "StringLiteral" else + if this instanceof HC_Nullptr then result = "Nullptr" else if this instanceof HC_Variable then result = "Variable" else if this instanceof HC_FieldAccess then result = "FieldAccess" else if this instanceof HC_Deref then result = "Deref" else @@ -140,6 +175,12 @@ class HashCons extends HCBase { if this instanceof HC_Unanalyzable then result = "Unanalyzable" else if this instanceof HC_NonmemberFunctionCall then result = "NonmemberFunctionCall" else if this instanceof HC_MemberFunctionCall then result = "MemberFunctionCall" else + if this instanceof HC_NewExpr then result = "NewExpr" else + if this instanceof HC_NewArrayExpr then result = "NewArrayExpr" else + if this instanceof HC_SizeofType then result = "SizeofTypeOperator" else + if this instanceof HC_SizeofExpr then result = "SizeofExprOperator" else + if this instanceof HC_AlignofType then result = "AlignofTypeOperator" else + if this instanceof HC_AlignofExpr then result = "AlignofExprOperator" else result = "error" } @@ -446,6 +487,195 @@ private predicate mk_ArgCons(Function fcn, HashCons hc, int i, HC_Args list, Fun ) } + +/** + * Holds if `fc` is a call to `fcn`, `fc`'s first `i` arguments have hash-cons + * `list`, and `fc`'s argument at index `i` has hash-cons `hc`. + */ +private predicate mk_AllocArgCons(Function fcn, HashCons hc, int i, HC_Alloc list, boolean aligned, FunctionCall fc) { + analyzableFunctionCall(fc) and + fc.getTarget() = fcn and + hc = hashCons(fc.getArgument(i).getFullyConverted()) and + ( + exists(HashCons head, HC_Alloc tail | + list = HC_AllocArgCons(fcn, head, i - 1, tail, aligned) and + mk_AllocArgCons(fcn, head, i - 1, tail, aligned, fc) and + ( + aligned = true and + i > 2 + or + aligned = false and + i > 1 + ) + ) + or + ( + aligned = true and + i = 2 + or + aligned = false and + i = 1 + ) and + list = HC_EmptyAllocArgs(fcn) + ) +} + +private predicate mk_HasInit(HashCons hc, NewOrNewArrayExpr new) { + hc = hashCons(new.(NewExpr).getInitializer()) or + hc = hashCons(new.(NewArrayExpr).getInitializer()) +} + +private predicate analyzableNewExpr(NewExpr new) { + strictcount(new.getAllocatedType()) = 1 and + ( + not exists(new.getAllocatorCall()) + or + strictcount(new.getAllocatorCall()) = 1 + ) and ( + not exists(new.getInitializer()) + or + strictcount(new.getInitializer()) = 1 + ) +} + +private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, boolean aligned, NewExpr new) { + analyzableNewExpr(new) and + t = new.getAllocatedType() and + ( + new.hasAlignedAllocation() and + aligned = true + or + not new.hasAlignedAllocation() and + aligned = false + ) + and + ( + exists(FunctionCall fc, HashCons head, HC_Alloc tail | + fc = new.getAllocatorCall() and + alloc = HC_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned) and + mk_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned, fc) + ) + or + exists(FunctionCall fc | + fc = new.getAllocatorCall() and + ( + aligned = true and + fc.getNumberOfArguments() = 2 + or + aligned = false and + fc.getNumberOfArguments() = 1 + ) and + alloc = HC_EmptyAllocArgs(fc.getTarget()) + ) + or + not exists(new.getAllocatorCall()) and + alloc = HC_NoAlloc() + ) + and + ( + init = HC_HasInit(hashCons(new.getInitializer())) + or + not exists(new.getInitializer()) and + init = HC_NoInit() + ) +} + +private predicate analyzableNewArrayExpr(NewArrayExpr new) { + strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and + strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and + ( + not exists(new.getAllocatorCall()) + or + strictcount(new.getAllocatorCall().getFullyConverted()) = 1 + ) and ( + not exists(new.getInitializer()) + or + strictcount(new.getInitializer().getFullyConverted()) = 1 + ) +} + +private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, boolean aligned, + NewArrayExpr new) { + analyzableNewArrayExpr(new) and + t = new.getAllocatedType() and + ( + new.hasAlignedAllocation() and + aligned = true + or + not new.hasAlignedAllocation() and + aligned = false + ) + and + ( + exists(FunctionCall fc, HashCons head, HC_Alloc tail | + fc = new.getAllocatorCall() and + alloc = HC_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned) and + mk_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned, fc) + ) + or + exists(FunctionCall fc | + fc = new.getAllocatorCall() and + ( + aligned = true and + fc.getNumberOfArguments() = 2 + or + aligned = false and + fc.getNumberOfArguments() = 1 + ) and + alloc = HC_EmptyAllocArgs(fc.getTarget()) + ) + or + not exists(new.getAllocatorCall()) and + alloc = HC_NoAlloc() + ) + and + ( + init = HC_HasInit(hashCons(new.getInitializer())) + or + not exists(new.getInitializer()) and + init = HC_NoInit() + ) +} + +private predicate analyzableSizeofType(SizeofTypeOperator e) { + strictcount(e.getType().getUnspecifiedType()) = 1 and + strictcount(e.getTypeOperand()) = 1 +} + +private predicate mk_SizeofType(Type t, SizeofTypeOperator e) { + analyzableSizeofType(e) and + t = e.getTypeOperand() +} + +private predicate analyzableSizeofExpr(Expr e) { + e instanceof SizeofExprOperator and + strictcount(e.getAChild().getFullyConverted()) = 1 +} + +private predicate mk_SizeofExpr(HashCons child, SizeofExprOperator e) { + analyzableSizeofExpr(e) and + child = hashCons(e.getAChild()) +} + +private predicate analyzableAlignofType(AlignofTypeOperator e) { + strictcount(e.getType().getUnspecifiedType()) = 1 and + strictcount(e.getTypeOperand()) = 1 +} + +private predicate mk_AlignofType(Type t, AlignofTypeOperator e) { + analyzableAlignofType(e) and + t = e.getTypeOperand() +} + +private predicate analyzableAlignofExpr(AlignofExprOperator e) { + strictcount(e.getExprOperand()) = 1 +} + +private predicate mk_AlignofExpr(HashCons child, AlignofExprOperator e) { + analyzableAlignofExpr(e) and + child = hashCons(e.getAChild()) +} + /** Gets the hash-cons of expression `e`. */ cached HashCons hashCons(Expr e) { exists (int val, Type t @@ -514,6 +744,36 @@ cached HashCons hashCons(Expr e) { result = HC_MemberFunctionCall(fcn, qual, args) ) or + exists(Type t, HC_Alloc alloc, HC_Init init, boolean aligned + | mk_NewExpr(t, alloc, init, aligned, e) and + result = HC_NewExpr(t, alloc, init) + ) + or + exists(Type t, HC_Alloc alloc, HC_Init init, boolean aligned + | mk_NewArrayExpr(t, alloc, init, aligned, e) and + result = HC_NewArrayExpr(t, alloc, init) + ) + or + exists(Type t + | mk_SizeofType(t, e) and + result = HC_SizeofType(t) + ) + or + exists(HashCons child + | mk_SizeofExpr(child, e) and + result = HC_SizeofExpr(child) + ) + or + exists(Type t + | mk_AlignofType(t, e) and + result = HC_AlignofType(t) + ) + or + exists(HashCons child + | mk_AlignofExpr(child, e) and + result = HC_AlignofExpr(child) + ) + or ( mk_Nullptr(e) and result = HC_Nullptr() @@ -545,5 +805,11 @@ predicate analyzableExpr(Expr e, string kind) { (analyzableArrayAccess(e) and kind = "ArrayAccess") or (analyzablePointerDereferenceExpr(e) and kind = "PointerDereferenceExpr") or (analyzableNonmemberFunctionCall(e) and kind = "NonmemberFunctionCall") or - (analyzableMemberFunctionCall(e) and kind = "MemberFunctionCall") + (analyzableMemberFunctionCall(e) and kind = "MemberFunctionCall") or + (analyzableNewExpr(e) and kind = "NewExpr") or + (analyzableNewArrayExpr(e) and kind = "NewArrayExpr") or + (analyzableSizeofType(e) and kind = "SizeofTypeOperator") or + (analyzableSizeofExpr(e) and kind = "SizeofExprOperator") or + (analyzableAlignofType(e) and kind = "AlignofTypeOperator") or + (analyzableAlignofExpr(e) and kind = "AlignofExprOperator") } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 7f4a5d6ed38..c1d9617206f 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -38,9 +38,9 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 270:c19-c19 271:c19-c19 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | -| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 | +| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 270:c15-c15 270:c22-c22 271:c15-c15 271:c22-c22 272:c15-c15 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | | test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | @@ -73,3 +73,27 @@ | test.cpp:179:17:179:17 | y | 179:c17-c17 179:c17-c17 | | test.cpp:179:17:179:21 | ... + ... | 179:c17-c21 179:c17-c21 | | test.cpp:185:17:185:17 | y | 185:c17-c17 185:c17-c17 | +| test.cpp:202:3:202:18 | sizeof(padded_t) | 202:c3-c18 204:c3-c18 | +| test.cpp:205:24:205:34 | sizeof(int) | 205:c24-c34 253:c25-c35 254:c25-c35 | +| test.cpp:206:25:206:43 | alignof(int_holder) | 206:c25-c43 206:c3-c21 | +| test.cpp:208:27:208:27 | x | 208:c27-c27 210:c10-c10 211:c11-c11 211:c24-c24 | +| test.cpp:209:10:209:15 | holder | 209:c10-c15 209:c29-c34 | +| test.cpp:209:10:209:18 | (...) | 209:c10-c18 209:c29-c37 | +| test.cpp:209:17:209:17 | x | 209:c17-c17 209:c36-c36 | +| test.cpp:209:22:209:37 | sizeof() | 209:c22-c37 209:c3-c18 | +| test.cpp:210:10:210:11 | (...) | 210:c10-c11 211:c11-c12 211:c24-c25 | +| test.cpp:211:16:211:25 | alignof() | 211:c16-c25 211:c3-c12 | +| test.cpp:247:3:247:12 | new | 247:c3-c12 248:c3-c12 | +| test.cpp:253:16:253:36 | new[] | 253:c16-c36 254:c16-c36 | +| test.cpp:256:3:256:21 | new | 256:c3-c21 257:c3-c21 | +| test.cpp:256:7:256:10 | (void *)... | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 | +| test.cpp:256:7:256:10 | ptr1 | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 | +| test.cpp:258:7:258:10 | (void *)... | 258:c7-c10 262:c11-c14 | +| test.cpp:258:7:258:10 | ptr2 | 258:c7-c10 262:c11-c14 | +| test.cpp:260:3:260:25 | new | 260:c3-c25 261:c3-c25 | +| test.cpp:260:7:260:8 | 32 | 260:c7-c8 261:c7-c8 262:c7-c8 264:c7-c8 265:c7-c8 267:c7-c8 268:c7-c8 270:c7-c8 271:c7-c8 272:c7-c8 | +| test.cpp:260:7:260:8 | (size_t)... | 260:c7-c8 261:c7-c8 262:c7-c8 264:c7-c8 265:c7-c8 267:c7-c8 268:c7-c8 270:c7-c8 271:c7-c8 272:c7-c8 | +| test.cpp:264:3:264:19 | new | 264:c3-c19 265:c3-c19 | +| test.cpp:267:3:267:23 | new[] | 267:c3-c23 268:c3-c23 | +| test.cpp:267:21:267:22 | 10 | 267:c21-c22 268:c21-c22 92:c15-c16 | +| test.cpp:272:19:272:19 | 3 | 272:c19-c19 35:c16-c16 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index ed31f21d35a..9522ab17b4b 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -184,3 +184,90 @@ int test13(int y) { int test14(int y) { return SQUARE(y); } + +typedef struct { + int x; + char y; +} padded_t; + +typedef struct { + int x; +} int_holder; + +typedef unsigned long size_t; + +void *malloc(size_t size); + +int test15(int x) { + sizeof(padded_t); + alignof(padded_t); + sizeof(padded_t); + sizeof(int_holder) + sizeof(int); + alignof(int_holder) + alignof(int_holder); + + int_holder holder = {x: x}; + sizeof(holder.x) + sizeof(holder.x); + sizeof(x); + alignof(x) + alignof(x); +} + +static void *operator new(size_t size) { + return malloc(size); +} + +static void *operator new(size_t size, void *placement) { + return placement; +} + +static void *operator new(size_t size, size_t alignment, void *placement) { + return placement; +} + +static void *operator new(size_t size, size_t alignment) { + return malloc(size); +} + +static void *operator new[](size_t size) { + return malloc(size); +} + +static void *operator new[](size_t size, void *placement) { + return placement; +} + +static void *operator new[](size_t size, size_t alignment, void *placement) { + return placement; +} + +static void *operator new[](size_t size, size_t alignment) { + return malloc(size); +} + +void test16() { + new int(1); + new int(1); + new int(2); + + int x; + + char *ptr1 = new char[sizeof(int)]; + char *ptr2 = new char[sizeof(int)]; + + new(ptr1) IntHolder; + new(ptr1) IntHolder; + new(ptr2) IntHolder; + + new(32, ptr1) IntHolder; + new(32, ptr1) IntHolder; + new(32, ptr2) IntHolder; + + new(32) IntHolder; + new(32) IntHolder; + + new(32) IntHolder[10]; + new(32) IntHolder[10]; + + new(32) int[2] {1, 2}; + new(32) int[2] {1, 2}; + new(32) int[2] {3, 4}; +} From 8f446aa9cce5ee4a3331dec8e22ce6e6675804c6 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 28 Aug 2018 13:41:46 -0700 Subject: [PATCH 22/73] C++: fix handling of aligned allocators --- .../code/cpp/valuenumbering/HashCons.qll | 44 ++++++++++++------- .../valuenumbering/HashCons/HashCons.expected | 20 ++++----- .../valuenumbering/HashCons/test.cpp | 1 + 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index fedaabf47e5..1f636bc5ab6 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -92,11 +92,11 @@ private cached newtype HCBase = mk_MemberFunctionCall(trg, qual, args, _) } or - HC_NewExpr(Type t, HC_Alloc alloc, HC_Init init) { - mk_NewExpr(t, alloc, init, _, _) + HC_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align) { + mk_NewExpr(t, alloc, init, align, _, _) } or - HC_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init) { - mk_NewArrayExpr(t, alloc, init, _, _) + HC_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align) { + mk_NewArrayExpr(t, alloc, init, align, _, _) } or HC_SizeofType(Type t) {mk_SizeofType(t, _)} @@ -123,11 +123,18 @@ private newtype HC_Alloc = } or HC_NoAlloc() + +/** Used to implement optional init on `new` expressions */ private newtype HC_Init = HC_NoInit() or HC_HasInit(HashCons hc) {mk_HasInit(hc, _)} +private newtype HC_Align = + HC_NoAlign() + or + HC_HasAlign(HashCons hc) {mk_HasAlign(hc, _)} + /** Used to implement hash-consing of argument lists */ private newtype HC_Args = HC_EmptyArgs(Function fcn) { @@ -525,6 +532,10 @@ private predicate mk_HasInit(HashCons hc, NewOrNewArrayExpr new) { hc = hashCons(new.(NewArrayExpr).getInitializer()) } +private predicate mk_HasAlign(HashCons hc, NewOrNewArrayExpr new) { + hc = hashCons(new.getAlignmentArgument()) +} + private predicate analyzableNewExpr(NewExpr new) { strictcount(new.getAllocatedType()) = 1 and ( @@ -538,14 +549,16 @@ private predicate analyzableNewExpr(NewExpr new) { ) } -private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, boolean aligned, NewExpr new) { +private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, boolean aligned, + NewExpr new) { analyzableNewExpr(new) and t = new.getAllocatedType() and ( - new.hasAlignedAllocation() and + align = HC_HasAlign(hashCons(new.getAlignmentArgument())) and aligned = true or not new.hasAlignedAllocation() and + align = HC_NoAlign() and aligned = false ) and @@ -594,15 +607,16 @@ private predicate analyzableNewArrayExpr(NewArrayExpr new) { ) } -private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, boolean aligned, - NewArrayExpr new) { +private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, + boolean aligned, NewArrayExpr new) { analyzableNewArrayExpr(new) and t = new.getAllocatedType() and ( - new.hasAlignedAllocation() and + align = HC_HasAlign(hashCons(new.getAlignmentArgument())) and aligned = true or not new.hasAlignedAllocation() and + align = HC_NoAlign() and aligned = false ) and @@ -744,14 +758,14 @@ cached HashCons hashCons(Expr e) { result = HC_MemberFunctionCall(fcn, qual, args) ) or - exists(Type t, HC_Alloc alloc, HC_Init init, boolean aligned - | mk_NewExpr(t, alloc, init, aligned, e) and - result = HC_NewExpr(t, alloc, init) + exists(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, boolean aligned + | mk_NewExpr(t, alloc, init, align, aligned, e) and + result = HC_NewExpr(t, alloc, init, align) ) or - exists(Type t, HC_Alloc alloc, HC_Init init, boolean aligned - | mk_NewArrayExpr(t, alloc, init, aligned, e) and - result = HC_NewArrayExpr(t, alloc, init) + exists(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, boolean aligned + | mk_NewArrayExpr(t, alloc, init, align, aligned, e) and + result = HC_NewArrayExpr(t, alloc, init, align) ) or exists(Type t diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index c1d9617206f..e2d2c341f07 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -38,9 +38,9 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 270:c19-c19 271:c19-c19 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c19-c19 272:c19-c19 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | -| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 270:c15-c15 270:c22-c22 271:c15-c15 271:c22-c22 272:c15-c15 | +| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c15-c15 271:c22-c22 272:c15-c15 272:c22-c22 273:c15-c15 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | | test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | @@ -86,14 +86,14 @@ | test.cpp:247:3:247:12 | new | 247:c3-c12 248:c3-c12 | | test.cpp:253:16:253:36 | new[] | 253:c16-c36 254:c16-c36 | | test.cpp:256:3:256:21 | new | 256:c3-c21 257:c3-c21 | -| test.cpp:256:7:256:10 | (void *)... | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 | -| test.cpp:256:7:256:10 | ptr1 | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 | +| test.cpp:256:7:256:10 | (void *)... | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 263:c11-c14 | +| test.cpp:256:7:256:10 | ptr1 | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 263:c11-c14 | | test.cpp:258:7:258:10 | (void *)... | 258:c7-c10 262:c11-c14 | | test.cpp:258:7:258:10 | ptr2 | 258:c7-c10 262:c11-c14 | | test.cpp:260:3:260:25 | new | 260:c3-c25 261:c3-c25 | -| test.cpp:260:7:260:8 | 32 | 260:c7-c8 261:c7-c8 262:c7-c8 264:c7-c8 265:c7-c8 267:c7-c8 268:c7-c8 270:c7-c8 271:c7-c8 272:c7-c8 | -| test.cpp:260:7:260:8 | (size_t)... | 260:c7-c8 261:c7-c8 262:c7-c8 264:c7-c8 265:c7-c8 267:c7-c8 268:c7-c8 270:c7-c8 271:c7-c8 272:c7-c8 | -| test.cpp:264:3:264:19 | new | 264:c3-c19 265:c3-c19 | -| test.cpp:267:3:267:23 | new[] | 267:c3-c23 268:c3-c23 | -| test.cpp:267:21:267:22 | 10 | 267:c21-c22 268:c21-c22 92:c15-c16 | -| test.cpp:272:19:272:19 | 3 | 272:c19-c19 35:c16-c16 | +| test.cpp:260:7:260:8 | 32 | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 | +| test.cpp:260:7:260:8 | (size_t)... | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 | +| test.cpp:265:3:265:19 | new | 265:c3-c19 266:c3-c19 | +| test.cpp:268:3:268:23 | new[] | 268:c3-c23 269:c3-c23 | +| test.cpp:268:21:268:22 | 10 | 268:c21-c22 269:c21-c22 92:c15-c16 | +| test.cpp:273:19:273:19 | 3 | 273:c19-c19 35:c16-c16 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 9522ab17b4b..e3980bdd11c 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -260,6 +260,7 @@ void test16() { new(32, ptr1) IntHolder; new(32, ptr1) IntHolder; new(32, ptr2) IntHolder; + new(16, ptr1) IntHolder; new(32) IntHolder; new(32) IntHolder; From 752f39b5374e5fe93e95c17432bfc4b796e578c4 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 29 Aug 2018 10:51:27 -0700 Subject: [PATCH 23/73] C++: initial support for aggregate initializers --- .../code/cpp/valuenumbering/HashCons.qll | 130 +++++++++++++++++- .../valuenumbering/HashCons/HashCons.expected | 13 +- .../valuenumbering/HashCons/test.cpp | 25 ++++ 3 files changed, 163 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 1f636bc5ab6..9cd9cc424ce 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -107,6 +107,14 @@ private cached newtype HCBase = or HC_AlignofExpr(HashCons child) {mk_AlignofExpr(child, _)} or + HC_ClassAggregateLiteral(Class c, HC_Fields hcf) { + mk_ClassAggregateLiteral(c, hcf, _) + } + or + HC_ArrayAggregateLiteral(Type t, HC_Array hca) { + mk_ArrayAggregateLiteral(t, hca, _) + } + or // Any expression that is not handled by the cases above is // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } @@ -144,6 +152,30 @@ private newtype HC_Args = mk_ArgCons(fcn, hc, i, list, _) } +/** + * Used to implement hash-consing of struct initizializers. + */ +private newtype HC_Fields = + HC_EmptyFields(Class c) { + exists(ClassAggregateLiteral cal | + c = cal.getType().getUnspecifiedType() + ) + } + or + HC_FieldCons(Class c, int i, Field f, HashCons hc, HC_Fields hcf) { + mk_FieldCons(c, i, f, hc, hcf, _) + } + +private newtype HC_Array = + HC_EmptyArray(Type t) { + exists(ArrayAggregateLiteral aal | + aal.getType() = t + ) + } + or + HC_ArrayCons(Type t, int i, HashCons hc, HC_Array hca) { + mk_ArrayCons(t, i, hc, hca, _) + } /** * HashCons is the hash-cons of an expression. The relationship between `Expr` * and `HC` is many-to-one: every `Expr` has exactly one `HC`, but multiple @@ -188,6 +220,8 @@ class HashCons extends HCBase { if this instanceof HC_SizeofExpr then result = "SizeofExprOperator" else if this instanceof HC_AlignofType then result = "AlignofTypeOperator" else if this instanceof HC_AlignofExpr then result = "AlignofExprOperator" else + if this instanceof HC_ArrayAggregateLiteral then result = "ArrayAggregateLiteral" else + if this instanceof HC_ClassAggregateLiteral then result = "ClassAggreagateLiteral" else result = "error" } @@ -690,6 +724,87 @@ private predicate mk_AlignofExpr(HashCons child, AlignofExprOperator e) { child = hashCons(e.getAChild()) } +private predicate mk_FieldCons(Class c, int i, Field f, HashCons hc, HC_Fields hcf, + ClassAggregateLiteral cal) { + analyzableClassAggregateLiteral(cal) and + cal.getType().getUnspecifiedType() = c and + exists(Expr e | + e = cal.getFieldExpr(f).getFullyConverted() and + e = cal.getChild(i).getFullyConverted() and + hc = hashCons(e) and + ( + exists(HashCons head, Field f2, HC_Fields tail | + hcf = HC_FieldCons(c, i-1, f2, head, tail) and + cal.getChild(i-1).getFullyConverted() = cal.getFieldExpr(f2).getFullyConverted() and + mk_FieldCons(c, i-1, f2, head, tail, cal) + + ) + or + i = 0 and + hcf = HC_EmptyFields(c) + ) + ) +} + +private predicate analyzableClassAggregateLiteral(ClassAggregateLiteral cal) { + forall(int i | + exists(cal.getChild(i)) | + strictcount(cal.getChild(i).getFullyConverted()) = 1 and + strictcount(Field f | cal.getChild(i) = cal.getFieldExpr(f)) = 1 + ) +} + +private predicate mk_ClassAggregateLiteral(Class c, HC_Fields hcf, ClassAggregateLiteral cal) { + analyzableClassAggregateLiteral(cal) and + c = cal.getType().getUnspecifiedType() and + ( + exists(HC_Fields tail, Expr e, Field f | + e = cal.getChild(cal.getNumChild() - 1).getFullyConverted() and + e = cal.getFieldExpr(f).getFullyConverted() and + hcf = HC_FieldCons(c, cal.getNumChild() - 1, f, hashCons(e), tail) and + mk_FieldCons(c, cal.getNumChild() - 1, f, hashCons(e), tail, cal) + ) + or + cal.getNumChild() = 0 and + hcf = HC_EmptyFields(c) + ) +} + +private predicate analyzableArrayAggregateLiteral(ArrayAggregateLiteral aal) { + forall(int i | + exists(aal.getChild(i)) | + strictcount(aal.getChild(i).getFullyConverted()) = 1 + ) +} + +private predicate mk_ArrayCons(Type t, int i, HashCons hc, HC_Array hca, ArrayAggregateLiteral aal) { + t = aal.getType().getUnspecifiedType() and + hc = hashCons(aal.getChild(i)) and + ( + exists(HC_Array tail, HashCons head | + hca = HC_ArrayCons(t, i - 1, head, tail) and + mk_ArrayCons(t, i-1, head, tail, aal) + ) + or + i = 0 and + hca = HC_EmptyArray(t) + ) +} + +private predicate mk_ArrayAggregateLiteral(Type t, HC_Array hca, ArrayAggregateLiteral aal) { + t = aal.getType().getUnspecifiedType() and + ( + exists(HashCons head, HC_Array tail | + hca = HC_ArrayCons(t, aal.getNumChild() - 1, head, tail) and + mk_ArrayCons(t, aal.getNumChild() - 1, head, tail, aal) + ) + or + aal.getNumChild() = 0 and + hca = HC_EmptyArray(t) + ) +} + + /** Gets the hash-cons of expression `e`. */ cached HashCons hashCons(Expr e) { exists (int val, Type t @@ -788,6 +903,16 @@ cached HashCons hashCons(Expr e) { result = HC_AlignofExpr(child) ) or + exists(Class c, HC_Fields hfc + | mk_ClassAggregateLiteral(c, hfc, e) and + result = HC_ClassAggregateLiteral(c, hfc) + ) + or + exists(Type t, HC_Array hca + | mk_ArrayAggregateLiteral(t, hca, e) and + result = HC_ArrayAggregateLiteral(t, hca) + ) + or ( mk_Nullptr(e) and result = HC_Nullptr() @@ -825,5 +950,8 @@ predicate analyzableExpr(Expr e, string kind) { (analyzableSizeofType(e) and kind = "SizeofTypeOperator") or (analyzableSizeofExpr(e) and kind = "SizeofExprOperator") or (analyzableAlignofType(e) and kind = "AlignofTypeOperator") or - (analyzableAlignofExpr(e) and kind = "AlignofExprOperator") + (analyzableAlignofExpr(e) and kind = "AlignofExprOperator") or + (analyzableClassAggregateLiteral(e) and kind = "ClassAggregateLiteral") or + (analyzableArrayAggregateLiteral(e) and kind = "ArrayAggregateLiteral") + } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index e2d2c341f07..a1def819e2c 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -38,9 +38,9 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c19-c19 272:c19-c19 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c19-c19 272:c19-c19 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | -| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c15-c15 271:c22-c22 272:c15-c15 272:c22-c22 273:c15-c15 | +| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c15-c15 271:c22-c22 272:c15-c15 272:c22-c22 273:c15-c15 274:c15-c15 275:c15-c15 275:c19-c19 275:c22-c22 277:c15-c15 278:c15-c15 289:c5-c5 293:c5-c5 296:c5-c5 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | | test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | @@ -91,9 +91,14 @@ | test.cpp:258:7:258:10 | (void *)... | 258:c7-c10 262:c11-c14 | | test.cpp:258:7:258:10 | ptr2 | 258:c7-c10 262:c11-c14 | | test.cpp:260:3:260:25 | new | 260:c3-c25 261:c3-c25 | -| test.cpp:260:7:260:8 | 32 | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 | -| test.cpp:260:7:260:8 | (size_t)... | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 | +| test.cpp:260:7:260:8 | 32 | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | +| test.cpp:260:7:260:8 | (size_t)... | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | | test.cpp:265:3:265:19 | new | 265:c3-c19 266:c3-c19 | | test.cpp:268:3:268:23 | new[] | 268:c3-c23 269:c3-c23 | | test.cpp:268:21:268:22 | 10 | 268:c21-c22 269:c21-c22 92:c15-c16 | +| test.cpp:271:3:271:23 | new[] | 271:c3-c23 272:c3-c23 | +| test.cpp:271:3:271:23 | {...} | 271:c3-c23 272:c3-c23 | | test.cpp:273:19:273:19 | 3 | 273:c19-c19 35:c16-c16 | +| test.cpp:277:3:277:19 | new[] | 277:c3-c19 278:c3-c19 | +| test.cpp:277:3:277:19 | {...} | 277:c3-c19 278:c3-c19 | +| test.cpp:287:15:290:3 | {...} | 287:c15-c3 291:c15-c3 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index e3980bdd11c..99319ed7eb8 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -271,4 +271,29 @@ void test16() { new(32) int[2] {1, 2}; new(32) int[2] {1, 2}; new(32) int[2] {3, 4}; + new(32) int[2] {1, 1}; + new(32) int[2] {2, 2}; + + new(32) int[2] {}; + new(32) int[2] {}; +} + +typedef struct point{ + int x; + int y; +} point_t; + +void test17() { + point_t p1 = { + 1, + 2 + }; + point_t p2 = { + 1, + 2 + }; + point_t p3 = { + 2, + 1 + }; } From 85cfb0202fed0503bf4c62e8e519a06f59d05e0d Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 29 Aug 2018 11:06:28 -0700 Subject: [PATCH 24/73] C++: add HashCons for delete expressions --- .../code/cpp/valuenumbering/HashCons.qll | 40 +++++++++++++++++-- .../valuenumbering/HashCons/HashCons.expected | 28 +++++++------ .../valuenumbering/HashCons/test.cpp | 16 ++++---- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 9cd9cc424ce..35311d4f9c6 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -115,6 +115,10 @@ private cached newtype HCBase = mk_ArrayAggregateLiteral(t, hca, _) } or + HC_DeleteExpr(HashCons child) {mk_DeleteExpr(child, _)} + or + HC_DeleteArrayExpr(HashCons child) {mk_DeleteArrayExpr(child, _)} + or // Any expression that is not handled by the cases above is // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } @@ -222,6 +226,8 @@ class HashCons extends HCBase { if this instanceof HC_AlignofExpr then result = "AlignofExprOperator" else if this instanceof HC_ArrayAggregateLiteral then result = "ArrayAggregateLiteral" else if this instanceof HC_ClassAggregateLiteral then result = "ClassAggreagateLiteral" else + if this instanceof HC_DeleteExpr then result = "DeleteExpr" else + if this instanceof HC_DeleteArrayExpr then result = "DeleteArrayExpr" else result = "error" } @@ -685,6 +691,24 @@ private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align ) } +private predicate analyzableDeleteExpr(DeleteExpr e) { + strictcount(e.getAChild().getFullyConverted()) = 1 +} + +private predicate mk_DeleteExpr(HashCons hc, DeleteExpr e) { + analyzableDeleteExpr(e) and + hc = hashCons(e.getAChild().getFullyConverted()) +} + +private predicate analyzableDeleteArrayExpr(DeleteArrayExpr e) { + strictcount(e.getAChild().getFullyConverted()) = 1 +} + +private predicate mk_DeleteArrayExpr(HashCons hc, DeleteArrayExpr e) { + analyzableDeleteArrayExpr(e) and + hc = hashCons(e.getAChild().getFullyConverted()) +} + private predicate analyzableSizeofType(SizeofTypeOperator e) { strictcount(e.getType().getUnspecifiedType()) = 1 and strictcount(e.getTypeOperand()) = 1 @@ -804,7 +828,6 @@ private predicate mk_ArrayAggregateLiteral(Type t, HC_Array hca, ArrayAggregateL ) } - /** Gets the hash-cons of expression `e`. */ cached HashCons hashCons(Expr e) { exists (int val, Type t @@ -913,6 +936,16 @@ cached HashCons hashCons(Expr e) { result = HC_ArrayAggregateLiteral(t, hca) ) or + exists(HashCons child + | mk_DeleteExpr(child, e) and + result = HC_DeleteExpr(child) + ) + or + exists(HashCons child + | mk_DeleteArrayExpr(child, e) and + result = HC_DeleteArrayExpr(child) + ) + or ( mk_Nullptr(e) and result = HC_Nullptr() @@ -952,6 +985,7 @@ predicate analyzableExpr(Expr e, string kind) { (analyzableAlignofType(e) and kind = "AlignofTypeOperator") or (analyzableAlignofExpr(e) and kind = "AlignofExprOperator") or (analyzableClassAggregateLiteral(e) and kind = "ClassAggregateLiteral") or - (analyzableArrayAggregateLiteral(e) and kind = "ArrayAggregateLiteral") - + (analyzableArrayAggregateLiteral(e) and kind = "ArrayAggregateLiteral") or + (analyzableDeleteExpr(e) and kind = "DeleteExpr") or + (analyzableDeleteArrayExpr(e) and kind = "DeleteArrayExpr") } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index a1def819e2c..6267a6bf60f 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -38,9 +38,9 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c19-c19 272:c19-c19 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c28-c28 272:c28-c28 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | -| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c15-c15 271:c22-c22 272:c15-c15 272:c22-c22 273:c15-c15 274:c15-c15 275:c15-c15 275:c19-c19 275:c22-c22 277:c15-c15 278:c15-c15 289:c5-c5 293:c5-c5 296:c5-c5 | +| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c24-c24 271:c31-c31 272:c24-c24 272:c31-c31 273:c24-c24 274:c15-c15 275:c15-c15 275:c19-c19 275:c22-c22 277:c15-c15 278:c15-c15 289:c5-c5 293:c5-c5 296:c5-c5 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | | test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | @@ -85,20 +85,22 @@ | test.cpp:211:16:211:25 | alignof() | 211:c16-c25 211:c3-c12 | | test.cpp:247:3:247:12 | new | 247:c3-c12 248:c3-c12 | | test.cpp:253:16:253:36 | new[] | 253:c16-c36 254:c16-c36 | -| test.cpp:256:3:256:21 | new | 256:c3-c21 257:c3-c21 | -| test.cpp:256:7:256:10 | (void *)... | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 263:c11-c14 | -| test.cpp:256:7:256:10 | ptr1 | 256:c7-c10 257:c7-c10 260:c11-c14 261:c11-c14 263:c11-c14 | -| test.cpp:258:7:258:10 | (void *)... | 258:c7-c10 262:c11-c14 | -| test.cpp:258:7:258:10 | ptr2 | 258:c7-c10 262:c11-c14 | -| test.cpp:260:3:260:25 | new | 260:c3-c25 261:c3-c25 | -| test.cpp:260:7:260:8 | 32 | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | -| test.cpp:260:7:260:8 | (size_t)... | 260:c7-c8 261:c7-c8 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c7-c8 272:c7-c8 273:c7-c8 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | +| test.cpp:256:3:256:28 | delete | 256:c3-c28 257:c3-c28 | +| test.cpp:256:10:256:28 | new | 256:c10-c28 257:c10-c28 | +| test.cpp:256:14:256:17 | (void *)... | 256:c14-c17 257:c14-c17 260:c18-c21 261:c20-c23 263:c11-c14 | +| test.cpp:256:14:256:17 | ptr1 | 256:c14-c17 257:c14-c17 260:c18-c21 261:c20-c23 263:c11-c14 | +| test.cpp:258:14:258:17 | (void *)... | 258:c14-c17 262:c11-c14 | +| test.cpp:258:14:258:17 | ptr2 | 258:c14-c17 262:c11-c14 | +| test.cpp:260:10:260:32 | new | 260:c10-c32 261:c12-c34 | +| test.cpp:260:14:260:15 | 32 | 260:c14-c15 261:c16-c17 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c16-c17 272:c16-c17 273:c16-c17 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | +| test.cpp:260:14:260:15 | (size_t)... | 260:c14-c15 261:c16-c17 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c16-c17 272:c16-c17 273:c16-c17 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | | test.cpp:265:3:265:19 | new | 265:c3-c19 266:c3-c19 | | test.cpp:268:3:268:23 | new[] | 268:c3-c23 269:c3-c23 | | test.cpp:268:21:268:22 | 10 | 268:c21-c22 269:c21-c22 92:c15-c16 | -| test.cpp:271:3:271:23 | new[] | 271:c3-c23 272:c3-c23 | -| test.cpp:271:3:271:23 | {...} | 271:c3-c23 272:c3-c23 | -| test.cpp:273:19:273:19 | 3 | 273:c19-c19 35:c16-c16 | +| test.cpp:271:3:271:32 | delete[] | 271:c3-c32 272:c3-c32 | +| test.cpp:271:12:271:32 | new[] | 271:c12-c32 272:c12-c32 | +| test.cpp:271:12:271:32 | {...} | 271:c12-c32 272:c12-c32 | +| test.cpp:273:28:273:28 | 3 | 273:c28-c28 35:c16-c16 | | test.cpp:277:3:277:19 | new[] | 277:c3-c19 278:c3-c19 | | test.cpp:277:3:277:19 | {...} | 277:c3-c19 278:c3-c19 | | test.cpp:287:15:290:3 | {...} | 287:c15-c3 291:c15-c3 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 99319ed7eb8..232eb380549 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -253,12 +253,12 @@ void test16() { char *ptr1 = new char[sizeof(int)]; char *ptr2 = new char[sizeof(int)]; - new(ptr1) IntHolder; - new(ptr1) IntHolder; - new(ptr2) IntHolder; + delete new(ptr1) IntHolder; + delete new(ptr1) IntHolder; + delete new(ptr2) IntHolder; - new(32, ptr1) IntHolder; - new(32, ptr1) IntHolder; + delete new(32, ptr1) IntHolder; + delete[] new(32, ptr1) IntHolder; new(32, ptr2) IntHolder; new(16, ptr1) IntHolder; @@ -268,9 +268,9 @@ void test16() { new(32) IntHolder[10]; new(32) IntHolder[10]; - new(32) int[2] {1, 2}; - new(32) int[2] {1, 2}; - new(32) int[2] {3, 4}; + delete[] new(32) int[2] {1, 2}; + delete[] new(32) int[2] {1, 2}; + delete[] new(32) int[2] {3, 4}; new(32) int[2] {1, 1}; new(32) int[2] {2, 2}; From 8189798f43aeb63ba52ad60e925bb02ebec4f4c4 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 29 Aug 2018 14:01:05 -0700 Subject: [PATCH 25/73] C++: HashCons for throw --- .../code/cpp/valuenumbering/HashCons.qll | 37 ++++++++++++++++++- .../valuenumbering/HashCons/HashCons.expected | 7 +++- .../valuenumbering/HashCons/test.cpp | 9 +++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 35311d4f9c6..850033a76b7 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -119,6 +119,10 @@ private cached newtype HCBase = or HC_DeleteArrayExpr(HashCons child) {mk_DeleteArrayExpr(child, _)} or + HC_ThrowExpr(HashCons child) {mk_ThrowExpr(child, _)} + or + HC_ReThrowExpr() + or // Any expression that is not handled by the cases above is // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } @@ -228,6 +232,8 @@ class HashCons extends HCBase { if this instanceof HC_ClassAggregateLiteral then result = "ClassAggreagateLiteral" else if this instanceof HC_DeleteExpr then result = "DeleteExpr" else if this instanceof HC_DeleteArrayExpr then result = "DeleteArrayExpr" else + if this instanceof HC_ThrowExpr then result = "ThrowExpr" else + if this instanceof HC_ReThrowExpr then result = "ReThrowExpr" else result = "error" } @@ -828,6 +834,23 @@ private predicate mk_ArrayAggregateLiteral(Type t, HC_Array hca, ArrayAggregateL ) } +private predicate analyzableThrowExpr(ThrowExpr te) { + strictcount(te.getExpr().getFullyConverted()) = 1 +} + +private predicate mk_ThrowExpr(HashCons hc, ThrowExpr te) { + analyzableThrowExpr(te) and + hc.getAnExpr() = te.getExpr().getFullyConverted() +} + +private predicate analyzableReThrowExpr(ReThrowExpr rte) { + any() +} + +private predicate mk_ReThrowExpr(ReThrowExpr te) { + any() +} + /** Gets the hash-cons of expression `e`. */ cached HashCons hashCons(Expr e) { exists (int val, Type t @@ -946,6 +969,16 @@ cached HashCons hashCons(Expr e) { result = HC_DeleteArrayExpr(child) ) or + exists(HashCons child + | mk_ThrowExpr(child, e) and + result = HC_ThrowExpr(child) + ) + or + ( + mk_ReThrowExpr(e) and + result = HC_ReThrowExpr() + ) + or ( mk_Nullptr(e) and result = HC_Nullptr() @@ -987,5 +1020,7 @@ predicate analyzableExpr(Expr e, string kind) { (analyzableClassAggregateLiteral(e) and kind = "ClassAggregateLiteral") or (analyzableArrayAggregateLiteral(e) and kind = "ArrayAggregateLiteral") or (analyzableDeleteExpr(e) and kind = "DeleteExpr") or - (analyzableDeleteArrayExpr(e) and kind = "DeleteArrayExpr") + (analyzableDeleteArrayExpr(e) and kind = "DeleteArrayExpr") or + (analyzableThrowExpr(e) and kind = "ThrowExpr") or + (analyzableReThrowExpr(e) and kind = "ReThrowExpr") } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 6267a6bf60f..84a50da1fd1 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -38,9 +38,9 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c28-c28 272:c28-c28 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c28-c28 272:c28-c28 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 302:c9-c9 303:c9-c9 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | -| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c24-c24 271:c31-c31 272:c24-c24 272:c31-c31 273:c24-c24 274:c15-c15 275:c15-c15 275:c19-c19 275:c22-c22 277:c15-c15 278:c15-c15 289:c5-c5 293:c5-c5 296:c5-c5 | +| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c24-c24 271:c31-c31 272:c24-c24 272:c31-c31 273:c24-c24 274:c15-c15 275:c15-c15 275:c19-c19 275:c22-c22 277:c15-c15 278:c15-c15 289:c5-c5 293:c5-c5 296:c5-c5 304:c9-c9 305:c9-c9 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | | test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | @@ -104,3 +104,6 @@ | test.cpp:277:3:277:19 | new[] | 277:c3-c19 278:c3-c19 | | test.cpp:277:3:277:19 | {...} | 277:c3-c19 278:c3-c19 | | test.cpp:287:15:290:3 | {...} | 287:c15-c3 291:c15-c3 | +| test.cpp:302:3:302:9 | throw ... | 302:c3-c9 303:c3-c9 | +| test.cpp:304:3:304:9 | throw ... | 304:c3-c9 305:c3-c9 | +| test.cpp:306:3:306:7 | re-throw exception | 306:c3-c7 307:c3-c7 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 232eb380549..36c8455a40b 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -297,3 +297,12 @@ void test17() { 1 }; } + +void test18() { + throw 1; + throw 1; + throw 2; + throw 2; + throw; + throw; +} From cfeed30a89ec44e01593b4ce96f08e8555e179cc Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 29 Aug 2018 14:25:09 -0700 Subject: [PATCH 26/73] C++: Hashcons tests for ArrayExpr --- .../valuenumbering/HashCons/HashCons.expected | 8 ++++++-- .../test/library-tests/valuenumbering/HashCons/test.cpp | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 84a50da1fd1..0680e7d8f2c 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -21,7 +21,6 @@ | test.cpp:43:7:43:24 | ... + ... | 43:c7-c24 45:c7-c24 | | test.cpp:43:12:43:13 | p1 | 43:c12-c13 45:c12-c13 | | test.cpp:43:17:43:24 | global03 | 43:c17-c24 45:c17-c24 | -| test.cpp:44:9:44:9 | 0 | 44:c9-c9 51:c25-c25 88:c12-c12 | | test.cpp:53:10:53:13 | (int)... | 53:c10-c13 56:c21-c24 | | test.cpp:53:10:53:13 | * ... | 53:c10-c13 56:c21-c24 | | test.cpp:53:11:53:13 | str | 53:c11-c13 56:c22-c24 | @@ -38,7 +37,7 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c28-c28 272:c28-c28 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 302:c9-c9 303:c9-c9 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c28-c28 272:c28-c28 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 302:c9-c9 303:c9-c9 313:c5-c5 314:c5-c5 316:c5-c5 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | | test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c24-c24 271:c31-c31 272:c24-c24 272:c31-c31 273:c24-c24 274:c15-c15 275:c15-c15 275:c19-c19 275:c22-c22 277:c15-c15 278:c15-c15 289:c5-c5 293:c5-c5 296:c5-c5 304:c9-c9 305:c9-c9 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | @@ -107,3 +106,8 @@ | test.cpp:302:3:302:9 | throw ... | 302:c3-c9 303:c3-c9 | | test.cpp:304:3:304:9 | throw ... | 304:c3-c9 305:c3-c9 | | test.cpp:306:3:306:7 | re-throw exception | 306:c3-c7 307:c3-c7 | +| test.cpp:311:3:311:3 | x | 311:c3-c3 312:c3-c3 313:c3-c3 314:c3-c3 | +| test.cpp:311:3:311:6 | access to array | 311:c3-c6 312:c3-c6 | +| test.cpp:311:5:311:5 | 0 | 311:c5-c5 312:c5-c5 315:c5-c5 44:c9-c9 51:c25-c25 88:c12-c12 | +| test.cpp:313:3:313:6 | access to array | 313:c3-c6 314:c3-c6 | +| test.cpp:315:3:315:3 | y | 315:c3-c3 316:c3-c3 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 36c8455a40b..5dcbf848921 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -306,3 +306,12 @@ void test18() { throw; throw; } + +void test19(int *x, int *y) { + x[0]; + x[0]; + x[1]; + x[1]; + y[0]; + y[1]; +} From 06a3e8fc76e87d30dcca01767dae1d08f331575c Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 29 Aug 2018 15:15:51 -0700 Subject: [PATCH 27/73] C++: Hashcons for ?:, ExprCall, and weird stuff --- .../code/cpp/valuenumbering/HashCons.qll | 178 +++++++++++++++--- .../valuenumbering/HashCons/HashCons.expected | 10 + .../valuenumbering/HashCons/test.cpp | 20 ++ 3 files changed, 180 insertions(+), 28 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 850033a76b7..0765ff92cf9 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -87,6 +87,9 @@ private cached newtype HCBase = HC_NonmemberFunctionCall(Function fcn, HC_Args args) { mk_NonmemberFunctionCall(fcn, args, _) } + or HC_ExprCall(HashCons hc, HC_Args args) { + mk_ExprCall(hc, args, _) + } or HC_MemberFunctionCall(Function trg, HashCons qual, HC_Args args) { mk_MemberFunctionCall(trg, qual, args, _) @@ -107,6 +110,12 @@ private cached newtype HCBase = or HC_AlignofExpr(HashCons child) {mk_AlignofExpr(child, _)} or + HC_UuidofOperator(Type t) {mk_UuidofOperator(t, _)} + or + HC_TypeidType(Type t) {mk_TypeidType(t, _)} + or + HC_TypeidExpr(HashCons child) {mk_TypeidExpr(child, _)} + or HC_ClassAggregateLiteral(Class c, HC_Fields hcf) { mk_ClassAggregateLiteral(c, hcf, _) } @@ -123,6 +132,14 @@ private cached newtype HCBase = or HC_ReThrowExpr() or + HC_ConditionalExpr(HashCons cond, HashCons trueHC, HashCons falseHC) { + mk_ConditionalExpr(cond, trueHC, falseHC, _) + } + or + HC_NoExceptExpr(HashCons child) { + mk_NoExceptExpr(child, _) + } + or // Any expression that is not handled by the cases above is // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } @@ -153,11 +170,11 @@ private newtype HC_Align = /** Used to implement hash-consing of argument lists */ private newtype HC_Args = - HC_EmptyArgs(Function fcn) { + HC_EmptyArgs() { any() } - or HC_ArgCons(Function fcn, HashCons hc, int i, HC_Args list) { - mk_ArgCons(fcn, hc, i, list, _) + or HC_ArgCons(HashCons hc, int i, HC_Args list) { + mk_ArgCons(hc, i, list, _) } /** @@ -228,12 +245,18 @@ class HashCons extends HCBase { if this instanceof HC_SizeofExpr then result = "SizeofExprOperator" else if this instanceof HC_AlignofType then result = "AlignofTypeOperator" else if this instanceof HC_AlignofExpr then result = "AlignofExprOperator" else + if this instanceof HC_UuidofOperator then result = "UuidofOperator" else + if this instanceof HC_TypeidType then result = "TypeidType" else + if this instanceof HC_TypeidExpr then result = "TypeidExpr" else if this instanceof HC_ArrayAggregateLiteral then result = "ArrayAggregateLiteral" else if this instanceof HC_ClassAggregateLiteral then result = "ClassAggreagateLiteral" else if this instanceof HC_DeleteExpr then result = "DeleteExpr" else if this instanceof HC_DeleteArrayExpr then result = "DeleteArrayExpr" else if this instanceof HC_ThrowExpr then result = "ThrowExpr" else if this instanceof HC_ReThrowExpr then result = "ReThrowExpr" else + if this instanceof HC_ExprCall then result = "ExprCall" else + if this instanceof HC_ConditionalExpr then result = "ConditionalExpr" else + if this instanceof HC_NoExceptExpr then result = "NoExceptExpr" else result = "error" } @@ -473,19 +496,40 @@ private predicate mk_NonmemberFunctionCall(Function fcn, HC_Args args, FunctionC analyzableNonmemberFunctionCall(fc) and ( exists(HashCons head, HC_Args tail | - args = HC_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail) and - mk_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail, fc) + args = HC_ArgCons(head, fc.getNumberOfArguments() - 1, tail) and + mk_ArgCons(head, fc.getNumberOfArguments() - 1, tail, fc) ) or fc.getNumberOfArguments() = 0 and - args = HC_EmptyArgs(fcn) + args = HC_EmptyArgs() + ) +} + +private predicate analyzableExprCall(ExprCall ec) { + forall(int i | + exists(ec.getArgument(i)) | + strictcount(ec.getArgument(i).getFullyConverted()) = 1 + ) and + strictcount(ec.getExpr().getFullyConverted()) = 1 +} + +private predicate mk_ExprCall(HashCons hc, HC_Args args, ExprCall ec) { + hc.getAnExpr() = ec.getExpr() and + ( + exists(HashCons head, HC_Args tail | + args = HC_ArgCons(head, ec.getNumberOfArguments() - 1, tail) and + mk_ArgCons(head, ec.getNumberOfArguments() - 1, tail, ec) + ) + or + ec.getNumberOfArguments() = 0 and + args = HC_EmptyArgs() ) } private predicate analyzableMemberFunctionCall( FunctionCall fc) { forall(int i | - exists(fc.getArgument(i)) | + exists(fc.getArgument(i)) | strictcount(fc.getArgument(i).getFullyConverted()) = 1 ) and strictcount(fc.getTarget()) = 1 and @@ -503,40 +547,39 @@ private predicate mk_MemberFunctionCall( hashCons(fc.getQualifier().getFullyConverted()) = qual and ( exists(HashCons head, HC_Args tail | - args = HC_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail) and - mk_ArgCons(fcn, head, fc.getNumberOfArguments() - 1, tail, fc) + args = HC_ArgCons(head, fc.getNumberOfArguments() - 1, tail) and + mk_ArgCons(head, fc.getNumberOfArguments() - 1, tail, fc) ) or fc.getNumberOfArguments() = 0 and - args = HC_EmptyArgs(fcn) + args = HC_EmptyArgs() ) } -private predicate analyzableFunctionCall( - FunctionCall fc -) { - analyzableNonmemberFunctionCall(fc) +private predicate analyzableCall(Call c) { + analyzableNonmemberFunctionCall(c) or - analyzableMemberFunctionCall(fc) + analyzableMemberFunctionCall(c) + or + analyzableExprCall(c) } /** * Holds if `fc` is a call to `fcn`, `fc`'s first `i` arguments have hash-cons * `list`, and `fc`'s argument at index `i` has hash-cons `hc`. */ -private predicate mk_ArgCons(Function fcn, HashCons hc, int i, HC_Args list, FunctionCall fc) { - analyzableFunctionCall(fc) and - fc.getTarget() = fcn and - hc = hashCons(fc.getArgument(i).getFullyConverted()) and +private predicate mk_ArgCons(HashCons hc, int i, HC_Args list,Call c) { + analyzableCall(c) and + hc = hashCons(c.getArgument(i).getFullyConverted()) and ( exists(HashCons head, HC_Args tail | - list = HC_ArgCons(fcn, head, i - 1, tail) and - mk_ArgCons(fcn, head, i - 1, tail, fc) and + list = HC_ArgCons(head, i - 1, tail) and + mk_ArgCons(head, i - 1, tail, c) and i > 0 ) or i = 0 and - list = HC_EmptyArgs(fcn) + list = HC_EmptyArgs() ) } @@ -545,14 +588,15 @@ private predicate mk_ArgCons(Function fcn, HashCons hc, int i, HC_Args list, Fun * Holds if `fc` is a call to `fcn`, `fc`'s first `i` arguments have hash-cons * `list`, and `fc`'s argument at index `i` has hash-cons `hc`. */ -private predicate mk_AllocArgCons(Function fcn, HashCons hc, int i, HC_Alloc list, boolean aligned, FunctionCall fc) { - analyzableFunctionCall(fc) and - fc.getTarget() = fcn and - hc = hashCons(fc.getArgument(i).getFullyConverted()) and +private predicate mk_AllocArgCons(Function fcn, HashCons hc, int i, HC_Alloc list, boolean aligned, + Call c) { + analyzableCall(c) and + c.getTarget() = fcn and + hc = hashCons(c.getArgument(i).getFullyConverted()) and ( exists(HashCons head, HC_Alloc tail | list = HC_AllocArgCons(fcn, head, i - 1, tail, aligned) and - mk_AllocArgCons(fcn, head, i - 1, tail, aligned, fc) and + mk_AllocArgCons(fcn, head, i - 1, tail, aligned, c) and ( aligned = true and i > 2 @@ -735,6 +779,34 @@ private predicate mk_SizeofExpr(HashCons child, SizeofExprOperator e) { child = hashCons(e.getAChild()) } +private predicate analyzableUuidofOperator(UuidofOperator e) { + strictcount(e.getTypeOperand()) = 1 +} + +private predicate mk_UuidofOperator(Type t, UuidofOperator e) { + analyzableUuidofOperator(e) and + t = e.getTypeOperand() +} + +private predicate analyzableTypeidType(TypeidOperator e) { + strictcount(e.getAChild()) = 0 +} + +private predicate mk_TypeidType(Type t, TypeidOperator e) { + analyzableTypeidType(e) and + t = e.getResultType() +} + +private predicate analyzableTypeidExpr(Expr e) { + e instanceof TypeidOperator and + strictcount(e.getAChild().getFullyConverted()) = 1 +} + +private predicate mk_TypeidExpr(HashCons child, TypeidOperator e) { + analyzableTypeidExpr(e) and + child = hashCons(e.getAChild()) +} + private predicate analyzableAlignofType(AlignofTypeOperator e) { strictcount(e.getType().getUnspecifiedType()) = 1 and strictcount(e.getTypeOperand()) = 1 @@ -851,6 +923,30 @@ private predicate mk_ReThrowExpr(ReThrowExpr te) { any() } +private predicate analyzableConditionalExpr(ConditionalExpr ce) { + strictcount(ce.getCondition().getFullyConverted()) = 1 and + strictcount(ce.getThen().getFullyConverted()) = 1 and + strictcount(ce.getElse().getFullyConverted()) = 1 +} + +private predicate mk_ConditionalExpr(HashCons cond, HashCons trueHc, HashCons falseHc, + ConditionalExpr ce) { + analyzableConditionalExpr(ce) and + cond.getAnExpr() = ce.getCondition() and + trueHc.getAnExpr() = ce.getThen() and + falseHc.getAnExpr() = ce.getElse() +} + +private predicate analyzableNoExceptExpr(NoExceptExpr nee) { + strictcount(nee.getAChild().getFullyConverted()) = 1 +} + +private predicate mk_NoExceptExpr(HashCons child, NoExceptExpr nee) { + analyzableNoExceptExpr(nee) and + nee.getExpr() = child.getAnExpr().getFullyConverted() +} + + /** Gets the hash-cons of expression `e`. */ cached HashCons hashCons(Expr e) { exists (int val, Type t @@ -914,6 +1010,11 @@ cached HashCons hashCons(Expr e) { result = HC_NonmemberFunctionCall(fcn, args) ) or + exists(HashCons hc, HC_Args args + | mk_ExprCall(hc, args, e) and + result = HC_ExprCall(hc, args) + ) + or exists(Function fcn, HashCons qual, HC_Args args | mk_MemberFunctionCall(fcn, qual, args, e) and result = HC_MemberFunctionCall(fcn, qual, args) @@ -940,6 +1041,16 @@ cached HashCons hashCons(Expr e) { ) or exists(Type t + | mk_TypeidType(t, e) and + result = HC_TypeidType(t) + ) + or + exists(HashCons child + | mk_TypeidExpr(child, e) and + result = HC_TypeidExpr(child) + ) + or + exists(Type t | mk_AlignofType(t, e) and result = HC_AlignofType(t) ) @@ -979,6 +1090,11 @@ cached HashCons hashCons(Expr e) { result = HC_ReThrowExpr() ) or + exists(HashCons cond, HashCons thenHC, HashCons elseHC + | mk_ConditionalExpr(cond, thenHC, elseHC, e) and + result = HC_ConditionalExpr(cond, thenHC, elseHC) + ) + or ( mk_Nullptr(e) and result = HC_Nullptr() @@ -1011,16 +1127,22 @@ predicate analyzableExpr(Expr e, string kind) { (analyzablePointerDereferenceExpr(e) and kind = "PointerDereferenceExpr") or (analyzableNonmemberFunctionCall(e) and kind = "NonmemberFunctionCall") or (analyzableMemberFunctionCall(e) and kind = "MemberFunctionCall") or + (analyzableExprCall(e) and kind = "ExprCall") or (analyzableNewExpr(e) and kind = "NewExpr") or (analyzableNewArrayExpr(e) and kind = "NewArrayExpr") or (analyzableSizeofType(e) and kind = "SizeofTypeOperator") or (analyzableSizeofExpr(e) and kind = "SizeofExprOperator") or (analyzableAlignofType(e) and kind = "AlignofTypeOperator") or (analyzableAlignofExpr(e) and kind = "AlignofExprOperator") or + (analyzableUuidofOperator(e) and kind = "UuidofOperator") or + (analyzableTypeidType(e) and kind = "TypeidType") or + (analyzableTypeidExpr(e) and kind = "TypeidExpr") or (analyzableClassAggregateLiteral(e) and kind = "ClassAggregateLiteral") or (analyzableArrayAggregateLiteral(e) and kind = "ArrayAggregateLiteral") or (analyzableDeleteExpr(e) and kind = "DeleteExpr") or (analyzableDeleteArrayExpr(e) and kind = "DeleteArrayExpr") or (analyzableThrowExpr(e) and kind = "ThrowExpr") or - (analyzableReThrowExpr(e) and kind = "ReThrowExpr") + (analyzableReThrowExpr(e) and kind = "ReThrowExpr") or + (analyzableConditionalExpr(e) and kind = "ConditionalExpr") or + (analyzableNoExceptExpr(e) and kind = "NoExceptExpr") } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 0680e7d8f2c..b985d73d559 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -111,3 +111,13 @@ | test.cpp:311:5:311:5 | 0 | 311:c5-c5 312:c5-c5 315:c5-c5 44:c9-c9 51:c25-c25 88:c12-c12 | | test.cpp:313:3:313:6 | access to array | 313:c3-c6 314:c3-c6 | | test.cpp:315:3:315:3 | y | 315:c3-c3 316:c3-c3 | +| test.cpp:323:3:323:11 | test_18_p | 323:c3-c11 324:c3-c11 | +| test.cpp:323:3:323:13 | call to expression | 323:c3-c13 324:c3-c13 | +| test.cpp:327:3:327:11 | test_19_p | 327:c3-c11 328:c3-c11 329:c3-c11 | +| test.cpp:327:3:327:17 | call to expression | 327:c3-c17 328:c3-c17 | +| test.cpp:327:13:327:13 | x | 327:c13-c13 328:c13-c13 329:c16-c16 | +| test.cpp:327:16:327:16 | y | 327:c16-c16 328:c16-c16 329:c13-c13 | +| test.cpp:333:3:333:8 | ... == ... | 333:c3-c8 334:c3-c8 336:c3-c8 | +| test.cpp:333:3:333:16 | ... ? ... : ... | 333:c3-c16 334:c3-c16 | +| test.cpp:333:12:333:12 | x | 333:c12-c12 333:c3-c3 334:c12-c12 334:c3-c3 335:c12-c12 335:c8-c8 336:c16-c16 336:c3-c3 | +| test.cpp:333:16:333:16 | y | 333:c16-c16 333:c8-c8 334:c16-c16 334:c8-c8 335:c16-c16 335:c3-c3 336:c12-c12 336:c8-c8 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 5dcbf848921..40635ed268c 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -315,3 +315,23 @@ void test19(int *x, int *y) { y[0]; y[1]; } + +void test20(int *x, int *y) { + void (*test_18_p)() = &test18; + void (*test_17_p)() = &test17; + void (*test_19_p)(int *, int *) = &test19; + test_18_p(); + test_18_p(); + test_17_p(); + + test_19_p(x, y); + test_19_p(x, y); + test_19_p(y, x); +} + +void test21(int x, int y) { + x == y ? x : y; + x == y ? x : y; + y == x ? x : y; + x == y ? y : x; +} From 246ae2d7e88fc0d427e36a3a4af62f971a16d048 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 30 Aug 2018 11:43:02 -0700 Subject: [PATCH 28/73] C++: fix performance of argument hash-consing --- .../code/cpp/valuenumbering/HashCons.qll | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 0765ff92cf9..d3542bcafe7 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -496,8 +496,7 @@ private predicate mk_NonmemberFunctionCall(Function fcn, HC_Args args, FunctionC analyzableNonmemberFunctionCall(fc) and ( exists(HashCons head, HC_Args tail | - args = HC_ArgCons(head, fc.getNumberOfArguments() - 1, tail) and - mk_ArgCons(head, fc.getNumberOfArguments() - 1, tail, fc) + mk_ArgConsInner(head, tail, fc.getNumberOfArguments() - 1, args, fc) ) or fc.getNumberOfArguments() = 0 and @@ -517,8 +516,7 @@ private predicate mk_ExprCall(HashCons hc, HC_Args args, ExprCall ec) { hc.getAnExpr() = ec.getExpr() and ( exists(HashCons head, HC_Args tail | - args = HC_ArgCons(head, ec.getNumberOfArguments() - 1, tail) and - mk_ArgCons(head, ec.getNumberOfArguments() - 1, tail, ec) + mk_ArgConsInner(head, tail, ec.getNumberOfArguments() - 1, args, ec) ) or ec.getNumberOfArguments() = 0 and @@ -547,8 +545,7 @@ private predicate mk_MemberFunctionCall( hashCons(fc.getQualifier().getFullyConverted()) = qual and ( exists(HashCons head, HC_Args tail | - args = HC_ArgCons(head, fc.getNumberOfArguments() - 1, tail) and - mk_ArgCons(head, fc.getNumberOfArguments() - 1, tail, fc) + mk_ArgConsInner(head, tail, fc.getNumberOfArguments() - 1, args, fc) ) or fc.getNumberOfArguments() = 0 and @@ -568,13 +565,12 @@ private predicate analyzableCall(Call c) { * Holds if `fc` is a call to `fcn`, `fc`'s first `i` arguments have hash-cons * `list`, and `fc`'s argument at index `i` has hash-cons `hc`. */ -private predicate mk_ArgCons(HashCons hc, int i, HC_Args list,Call c) { +private predicate mk_ArgCons(HashCons hc, int i, HC_Args list, Call c) { analyzableCall(c) and hc = hashCons(c.getArgument(i).getFullyConverted()) and ( exists(HashCons head, HC_Args tail | - list = HC_ArgCons(head, i - 1, tail) and - mk_ArgCons(head, i - 1, tail, c) and + mk_ArgConsInner(head, tail, i-1, list, c) and i > 0 ) or @@ -583,6 +579,12 @@ private predicate mk_ArgCons(HashCons hc, int i, HC_Args list,Call c) { ) } +// avoid a join ordering issue +pragma[noopt] +private predicate mk_ArgConsInner(HashCons head, HC_Args tail, int i, HC_Args list, Call c) { + list = HC_ArgCons(head, i, tail) and + mk_ArgCons(head, i, tail, c) +} /** * Holds if `fc` is a call to `fcn`, `fc`'s first `i` arguments have hash-cons From fa9eeea302a6fa77b25149bcf59ee4e5d72dd10f Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 29 Aug 2018 12:33:50 -0700 Subject: [PATCH 29/73] C++: remove implicit this handling in HashCons --- .../code/cpp/valuenumbering/HashCons.qll | 55 ++++++------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index d3542bcafe7..82d757c5aca 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -56,20 +56,23 @@ private cached newtype HCBase = } or HC_FieldAccess(HashCons s, Field f) { - mk_DotFieldAccess(s,f,_) or - mk_PointerFieldAccess_with_deref(s,f,_) or - mk_ImplicitThisFieldAccess_with_deref(s,f,_) + mk_DotFieldAccess(s,f,_) } or HC_Deref(HashCons p) { - mk_Deref(p,_) or - mk_PointerFieldAccess(p,_,_) or - mk_ImplicitThisFieldAccess_with_qualifier(p,_,_) + mk_Deref(p,_) + } + or + HC_PointerFieldAccess(HashCons qual, Field target) { + mk_PointerFieldAccess(qual, target, _) } or HC_ThisExpr(Function fcn) { - mk_ThisExpr(fcn,_) or - mk_ImplicitThisFieldAccess(fcn,_,_) + mk_ThisExpr(fcn,_) + } + or + HC_ImplicitThisFieldAccess (Function fcn, Field target){ + mk_ImplicitThisFieldAccess(fcn, target, _) } or HC_Conversion(Type t, HashCons child) { mk_Conversion(t, child, _) } @@ -368,17 +371,6 @@ private predicate mk_PointerFieldAccess( qualifier = hashCons(access.getQualifier().getFullyConverted()) } -/* - * `obj->field` is equivalent to `(*obj).field`, so we need to wrap an - * extra `HC_Deref` around the qualifier. - */ -private predicate mk_PointerFieldAccess_with_deref (HashCons new_qualifier, Field target, - PointerFieldAccess access) { - exists (HashCons qualifier - | mk_PointerFieldAccess(qualifier, target, access) and - new_qualifier = HC_Deref(qualifier)) -} - private predicate analyzableImplicitThisFieldAccess(ImplicitThisFieldAccess access) { strictcount (access.getTarget()) = 1 and strictcount (access.getEnclosingFunction()) = 1 @@ -391,21 +383,6 @@ private predicate mk_ImplicitThisFieldAccess(Function fcn, Field target, fcn = access.getEnclosingFunction() } -private predicate mk_ImplicitThisFieldAccess_with_qualifier( HashCons qualifier, Field target, - ImplicitThisFieldAccess access) { - exists (Function fcn - | mk_ImplicitThisFieldAccess(fcn, target, access) and - qualifier = HC_ThisExpr(fcn)) -} - -private predicate mk_ImplicitThisFieldAccess_with_deref(HashCons new_qualifier, Field target, - ImplicitThisFieldAccess access) { - exists (HashCons qualifier - | mk_ImplicitThisFieldAccess_with_qualifier( - qualifier, target, access) and - new_qualifier = HC_Deref(qualifier)) -} - private predicate analyzableVariable(VariableAccess access) { not (access instanceof FieldAccess) and strictcount (access.getTarget()) = 1 @@ -976,12 +953,12 @@ cached HashCons hashCons(Expr e) { result = HC_FieldAccess(qualifier, target)) or exists (HashCons qualifier, Field target - | mk_PointerFieldAccess_with_deref(qualifier, target, e) and - result = HC_FieldAccess(qualifier, target)) + | mk_PointerFieldAccess(qualifier, target, e) and + result = HC_PointerFieldAccess(qualifier, target)) or - exists (HashCons qualifier, Field target - | mk_ImplicitThisFieldAccess_with_deref(qualifier, target, e) and - result = HC_FieldAccess(qualifier, target)) + exists (Function fcn, Field target + | mk_ImplicitThisFieldAccess(fcn, target, e) and + result = HC_ImplicitThisFieldAccess(fcn, target)) or exists (Function fcn | mk_ThisExpr(fcn, e) and From 9f476e585acb80e6225a75bd2fb21b86050fb209 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 30 Aug 2018 13:22:51 -0700 Subject: [PATCH 30/73] C++: Simplify some code --- .../code/cpp/valuenumbering/HashCons.qll | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 82d757c5aca..7a7b33ade60 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -197,7 +197,7 @@ private newtype HC_Fields = private newtype HC_Array = HC_EmptyArray(Type t) { exists(ArrayAggregateLiteral aal | - aal.getType() = t + aal.getType().getUnspecifiedType() = t ) } or @@ -602,20 +602,13 @@ private predicate mk_HasInit(HashCons hc, NewOrNewArrayExpr new) { } private predicate mk_HasAlign(HashCons hc, NewOrNewArrayExpr new) { - hc = hashCons(new.getAlignmentArgument()) + hc = hashCons(new.getAlignmentArgument().getFullyConverted()) } private predicate analyzableNewExpr(NewExpr new) { strictcount(new.getAllocatedType()) = 1 and - ( - not exists(new.getAllocatorCall()) - or - strictcount(new.getAllocatorCall()) = 1 - ) and ( - not exists(new.getInitializer()) - or - strictcount(new.getInitializer()) = 1 - ) + count(new.getAllocatorCall().getFullyConverted()) <= 1 and + count(new.getInitializer().getFullyConverted()) <= 1 } private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, boolean aligned, @@ -623,7 +616,7 @@ private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align alig analyzableNewExpr(new) and t = new.getAllocatedType() and ( - align = HC_HasAlign(hashCons(new.getAlignmentArgument())) and + align = HC_HasAlign(hashCons(new.getAlignmentArgument().getFullyConverted())) and aligned = true or not new.hasAlignedAllocation() and @@ -664,16 +657,8 @@ private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align alig private predicate analyzableNewArrayExpr(NewArrayExpr new) { strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and - strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and - ( - not exists(new.getAllocatorCall()) - or - strictcount(new.getAllocatorCall().getFullyConverted()) = 1 - ) and ( - not exists(new.getInitializer()) - or - strictcount(new.getInitializer().getFullyConverted()) = 1 - ) + count(new.getAllocatorCall().getFullyConverted()) <= 1 and + count(new.getInitializer().getFullyConverted()) <= 1 } private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, @@ -681,7 +666,7 @@ private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align analyzableNewArrayExpr(new) and t = new.getAllocatedType() and ( - align = HC_HasAlign(hashCons(new.getAlignmentArgument())) and + align = HC_HasAlign(hashCons(new.getAlignmentArgument().getFullyConverted())) and aligned = true or not new.hasAlignedAllocation() and From c42ecfe8f9605f55b8b6cc912df36deaf7a1502c Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 30 Aug 2018 14:48:57 -0700 Subject: [PATCH 31/73] C++: Simplify HashCons for new and handle extents --- .../code/cpp/valuenumbering/HashCons.qll | 212 +++++++----------- .../valuenumbering/HashCons/HashCons.expected | 93 ++++---- .../valuenumbering/HashCons/test.cpp | 15 +- 3 files changed, 142 insertions(+), 178 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 7a7b33ade60..e3ac972a569 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -98,11 +98,17 @@ private cached newtype HCBase = mk_MemberFunctionCall(trg, qual, args, _) } or - HC_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align) { - mk_NewExpr(t, alloc, init, align, _, _) - } or - HC_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align) { - mk_NewArrayExpr(t, alloc, init, align, _, _) + // Hack to get around argument 0 of allocator calls being an error expression + HC_AllocatorArgZero(Type t) { + mk_AllocatorArgZero(t, _) + } + or + HC_NewExpr(Type t, HC_Alloc alloc, HC_Init init) { + mk_NewExpr(t, alloc, init, _) + } + or + HC_NewArrayExpr(Type t, HC_Alloc alloc, HC_Extent extent, HC_Init init) { + mk_NewArrayExpr(t, alloc, extent, init, _) } or HC_SizeofType(Type t) {mk_SizeofType(t, _)} @@ -147,30 +153,27 @@ private cached newtype HCBase = // given a unique number based on the expression itself. HC_Unanalyzable(Expr e) { not analyzableExpr(e,_) } -/** Used to implement hash-consing of `new` placement argument lists */ -private newtype HC_Alloc = - HC_EmptyAllocArgs(Function fcn) { - exists(NewOrNewArrayExpr n | - n.getAllocator() = fcn - ) - } - or HC_AllocArgCons(Function fcn, HashCons hc, int i, HC_Alloc list, boolean aligned) { - mk_AllocArgCons(fcn, hc, i, list, aligned, _) - } - or - HC_NoAlloc() - /** Used to implement optional init on `new` expressions */ private newtype HC_Init = HC_NoInit() or HC_HasInit(HashCons hc) {mk_HasInit(hc, _)} -private newtype HC_Align = - HC_NoAlign() +/** + * Used to implement optional allocator call on `new` expressions + */ +private newtype HC_Alloc = + HC_NoAlloc() or - HC_HasAlign(HashCons hc) {mk_HasAlign(hc, _)} - + HC_HasAlloc(HashCons hc) {mk_HasAlloc(hc, _)} + +/** + * Used to implement optional extent expression on `new[]` exprtessions + */ +private newtype HC_Extent = + HC_NoExtent() + or + HC_HasExtent(HashCons hc) {mk_HasExtent(hc, _)} /** Used to implement hash-consing of argument lists */ private newtype HC_Args = HC_EmptyArgs() { @@ -260,6 +263,7 @@ class HashCons extends HCBase { if this instanceof HC_ExprCall then result = "ExprCall" else if this instanceof HC_ConditionalExpr then result = "ConditionalExpr" else if this instanceof HC_NoExceptExpr then result = "NoExceptExpr" else + if this instanceof HC_AllocatorArgZero then result = "AllocatorArgZero" else result = "error" } @@ -563,94 +567,57 @@ private predicate mk_ArgConsInner(HashCons head, HC_Args tail, int i, HC_Args li mk_ArgCons(head, i, tail, c) } -/** - * Holds if `fc` is a call to `fcn`, `fc`'s first `i` arguments have hash-cons - * `list`, and `fc`'s argument at index `i` has hash-cons `hc`. +/* + * The 0th argument of an allocator call in a new expression is always an error expression; + * this works around it */ -private predicate mk_AllocArgCons(Function fcn, HashCons hc, int i, HC_Alloc list, boolean aligned, - Call c) { - analyzableCall(c) and - c.getTarget() = fcn and - hc = hashCons(c.getArgument(i).getFullyConverted()) and - ( - exists(HashCons head, HC_Alloc tail | - list = HC_AllocArgCons(fcn, head, i - 1, tail, aligned) and - mk_AllocArgCons(fcn, head, i - 1, tail, aligned, c) and - ( - aligned = true and - i > 2 - or - aligned = false and - i > 1 - ) - ) - or - ( - aligned = true and - i = 2 - or - aligned = false and - i = 1 - ) and - list = HC_EmptyAllocArgs(fcn) +private predicate analyzableAllocatorArgZero(ErrorExpr e) { + exists(NewOrNewArrayExpr new | + new.getAllocatorCall().getChild(0) = e + ) +} + +private predicate mk_AllocatorArgZero(Type t, ErrorExpr e) { + exists(NewOrNewArrayExpr new | + new.getAllocatorCall().getChild(0) = e and + t = new.getType().getUnspecifiedType() ) } private predicate mk_HasInit(HashCons hc, NewOrNewArrayExpr new) { - hc = hashCons(new.(NewExpr).getInitializer()) or - hc = hashCons(new.(NewArrayExpr).getInitializer()) + hc = hashCons(new.(NewExpr).getInitializer().getFullyConverted()) or + hc = hashCons(new.(NewArrayExpr).getInitializer().getFullyConverted()) } -private predicate mk_HasAlign(HashCons hc, NewOrNewArrayExpr new) { - hc = hashCons(new.getAlignmentArgument().getFullyConverted()) +private predicate mk_HasAlloc(HashCons hc, NewOrNewArrayExpr new) { + hc = hashCons(new.(NewExpr).getAllocatorCall().getFullyConverted()) or + hc = hashCons(new.(NewArrayExpr).getAllocatorCall().getFullyConverted()) +} + +private predicate mk_HasExtent(HashCons hc, NewArrayExpr new) { + hc = hashCons(new.(NewArrayExpr).getExtent().getFullyConverted()) } private predicate analyzableNewExpr(NewExpr new) { - strictcount(new.getAllocatedType()) = 1 and + strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and count(new.getAllocatorCall().getFullyConverted()) <= 1 and count(new.getInitializer().getFullyConverted()) <= 1 } -private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, boolean aligned, - NewExpr new) { +private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, NewExpr new) { analyzableNewExpr(new) and - t = new.getAllocatedType() and + t = new.getAllocatedType().getUnspecifiedType() and ( - align = HC_HasAlign(hashCons(new.getAlignmentArgument().getFullyConverted())) and - aligned = true + alloc = HC_HasAlloc(hashCons(new.getAllocatorCall().getFullyConverted())) or - not new.hasAlignedAllocation() and - align = HC_NoAlign() and - aligned = false - ) - and - ( - exists(FunctionCall fc, HashCons head, HC_Alloc tail | - fc = new.getAllocatorCall() and - alloc = HC_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned) and - mk_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned, fc) - ) - or - exists(FunctionCall fc | - fc = new.getAllocatorCall() and - ( - aligned = true and - fc.getNumberOfArguments() = 2 - or - aligned = false and - fc.getNumberOfArguments() = 1 - ) and - alloc = HC_EmptyAllocArgs(fc.getTarget()) - ) - or - not exists(new.getAllocatorCall()) and + not exists(new.getAllocatorCall().getFullyConverted()) and alloc = HC_NoAlloc() ) and ( - init = HC_HasInit(hashCons(new.getInitializer())) + init = HC_HasInit(hashCons(new.getInitializer().getFullyConverted())) or - not exists(new.getInitializer()) and + not exists(new.getInitializer().getFullyConverted()) and init = HC_NoInit() ) } @@ -658,51 +625,35 @@ private predicate mk_NewExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align alig private predicate analyzableNewArrayExpr(NewArrayExpr new) { strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and count(new.getAllocatorCall().getFullyConverted()) <= 1 and - count(new.getInitializer().getFullyConverted()) <= 1 + count(new.getInitializer().getFullyConverted()) <= 1 and + count(new.(NewArrayExpr).getExtent().getFullyConverted()) <= 1 } -private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, - boolean aligned, NewArrayExpr new) { +private predicate mk_NewArrayExpr(Type t, HC_Alloc alloc, HC_Extent extent, HC_Init init, NewArrayExpr new) { analyzableNewArrayExpr(new) and t = new.getAllocatedType() and + ( - align = HC_HasAlign(hashCons(new.getAlignmentArgument().getFullyConverted())) and - aligned = true + alloc = HC_HasAlloc(hashCons(new.getAllocatorCall().getFullyConverted())) or - not new.hasAlignedAllocation() and - align = HC_NoAlign() and - aligned = false - ) - and - ( - exists(FunctionCall fc, HashCons head, HC_Alloc tail | - fc = new.getAllocatorCall() and - alloc = HC_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned) and - mk_AllocArgCons(fc.getTarget(), head, fc.getNumberOfArguments() - 1, tail, aligned, fc) - ) - or - exists(FunctionCall fc | - fc = new.getAllocatorCall() and - ( - aligned = true and - fc.getNumberOfArguments() = 2 - or - aligned = false and - fc.getNumberOfArguments() = 1 - ) and - alloc = HC_EmptyAllocArgs(fc.getTarget()) - ) - or - not exists(new.getAllocatorCall()) and + not exists(new.getAllocatorCall().getFullyConverted()) and alloc = HC_NoAlloc() ) and ( - init = HC_HasInit(hashCons(new.getInitializer())) + init = HC_HasInit(hashCons(new.getInitializer().getFullyConverted())) or - not exists(new.getInitializer()) and + not exists(new.getInitializer().getFullyConverted()) and init = HC_NoInit() ) + and + + ( + extent = HC_HasExtent(hashCons(new.getExtent().getFullyConverted())) + or + not exists(new.getExtent().getFullyConverted()) and + extent = HC_NoExtent() + ) } private predicate analyzableDeleteExpr(DeleteExpr e) { @@ -984,14 +935,20 @@ cached HashCons hashCons(Expr e) { result = HC_MemberFunctionCall(fcn, qual, args) ) or - exists(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, boolean aligned - | mk_NewExpr(t, alloc, init, align, aligned, e) and - result = HC_NewExpr(t, alloc, init, align) + // works around an extractor issue class + exists(Type t + | mk_AllocatorArgZero(t, e) and + result = HC_AllocatorArgZero(t) ) or - exists(Type t, HC_Alloc alloc, HC_Init init, HC_Align align, boolean aligned - | mk_NewArrayExpr(t, alloc, init, align, aligned, e) and - result = HC_NewArrayExpr(t, alloc, init, align) + exists(Type t, HC_Alloc alloc, HC_Init init + | mk_NewExpr(t, alloc, init, e) and + result = HC_NewExpr(t, alloc, init) + ) + or + exists(Type t, HC_Alloc alloc, HC_Extent extent, HC_Init init + | mk_NewArrayExpr(t, alloc, extent, init, e) and + result = HC_NewArrayExpr(t, alloc, extent, init) ) or exists(Type t @@ -1108,5 +1065,6 @@ predicate analyzableExpr(Expr e, string kind) { (analyzableThrowExpr(e) and kind = "ThrowExpr") or (analyzableReThrowExpr(e) and kind = "ReThrowExpr") or (analyzableConditionalExpr(e) and kind = "ConditionalExpr") or - (analyzableNoExceptExpr(e) and kind = "NoExceptExpr") + (analyzableNoExceptExpr(e) and kind = "NoExceptExpr") or + (analyzableAllocatorArgZero(e) and kind = "AllocatorArgZero") } diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index b985d73d559..48adfedf87e 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -37,9 +37,9 @@ | test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 247:c11-c11 248:c11-c11 271:c28-c28 272:c28-c28 274:c19-c19 274:c22-c22 288:c5-c5 292:c5-c5 297:c5-c5 302:c9-c9 303:c9-c9 313:c5-c5 314:c5-c5 316:c5-c5 | +| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 239:c11-c11 240:c11-c11 263:c28-c28 264:c28-c28 266:c19-c19 266:c22-c22 285:c5-c5 289:c5-c5 294:c5-c5 299:c9-c9 300:c9-c9 310:c5-c5 311:c5-c5 313:c5-c5 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | -| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 249:c11-c11 271:c24-c24 271:c31-c31 272:c24-c24 272:c31-c31 273:c24-c24 274:c15-c15 275:c15-c15 275:c19-c19 275:c22-c22 277:c15-c15 278:c15-c15 289:c5-c5 293:c5-c5 296:c5-c5 304:c9-c9 305:c9-c9 | +| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 241:c11-c11 263:c24-c24 263:c31-c31 264:c24-c24 264:c31-c31 265:c24-c24 266:c15-c15 267:c15-c15 267:c19-c19 267:c22-c22 269:c15-c15 270:c15-c15 286:c5-c5 290:c5-c5 293:c5-c5 301:c9-c9 302:c9-c9 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | | test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | | test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | @@ -73,7 +73,7 @@ | test.cpp:179:17:179:21 | ... + ... | 179:c17-c21 179:c17-c21 | | test.cpp:185:17:185:17 | y | 185:c17-c17 185:c17-c17 | | test.cpp:202:3:202:18 | sizeof(padded_t) | 202:c3-c18 204:c3-c18 | -| test.cpp:205:24:205:34 | sizeof(int) | 205:c24-c34 253:c25-c35 254:c25-c35 | +| test.cpp:205:24:205:34 | sizeof(int) | 205:c24-c34 245:c25-c35 246:c25-c35 | | test.cpp:206:25:206:43 | alignof(int_holder) | 206:c25-c43 206:c3-c21 | | test.cpp:208:27:208:27 | x | 208:c27-c27 210:c10-c10 211:c11-c11 211:c24-c24 | | test.cpp:209:10:209:15 | holder | 209:c10-c15 209:c29-c34 | @@ -82,42 +82,51 @@ | test.cpp:209:22:209:37 | sizeof() | 209:c22-c37 209:c3-c18 | | test.cpp:210:10:210:11 | (...) | 210:c10-c11 211:c11-c12 211:c24-c25 | | test.cpp:211:16:211:25 | alignof() | 211:c16-c25 211:c3-c12 | -| test.cpp:247:3:247:12 | new | 247:c3-c12 248:c3-c12 | -| test.cpp:253:16:253:36 | new[] | 253:c16-c36 254:c16-c36 | -| test.cpp:256:3:256:28 | delete | 256:c3-c28 257:c3-c28 | -| test.cpp:256:10:256:28 | new | 256:c10-c28 257:c10-c28 | -| test.cpp:256:14:256:17 | (void *)... | 256:c14-c17 257:c14-c17 260:c18-c21 261:c20-c23 263:c11-c14 | -| test.cpp:256:14:256:17 | ptr1 | 256:c14-c17 257:c14-c17 260:c18-c21 261:c20-c23 263:c11-c14 | -| test.cpp:258:14:258:17 | (void *)... | 258:c14-c17 262:c11-c14 | -| test.cpp:258:14:258:17 | ptr2 | 258:c14-c17 262:c11-c14 | -| test.cpp:260:10:260:32 | new | 260:c10-c32 261:c12-c34 | -| test.cpp:260:14:260:15 | 32 | 260:c14-c15 261:c16-c17 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c16-c17 272:c16-c17 273:c16-c17 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | -| test.cpp:260:14:260:15 | (size_t)... | 260:c14-c15 261:c16-c17 262:c7-c8 265:c7-c8 266:c7-c8 268:c7-c8 269:c7-c8 271:c16-c17 272:c16-c17 273:c16-c17 274:c7-c8 275:c7-c8 277:c7-c8 278:c7-c8 | -| test.cpp:265:3:265:19 | new | 265:c3-c19 266:c3-c19 | -| test.cpp:268:3:268:23 | new[] | 268:c3-c23 269:c3-c23 | -| test.cpp:268:21:268:22 | 10 | 268:c21-c22 269:c21-c22 92:c15-c16 | -| test.cpp:271:3:271:32 | delete[] | 271:c3-c32 272:c3-c32 | -| test.cpp:271:12:271:32 | new[] | 271:c12-c32 272:c12-c32 | -| test.cpp:271:12:271:32 | {...} | 271:c12-c32 272:c12-c32 | -| test.cpp:273:28:273:28 | 3 | 273:c28-c28 35:c16-c16 | -| test.cpp:277:3:277:19 | new[] | 277:c3-c19 278:c3-c19 | -| test.cpp:277:3:277:19 | {...} | 277:c3-c19 278:c3-c19 | -| test.cpp:287:15:290:3 | {...} | 287:c15-c3 291:c15-c3 | -| test.cpp:302:3:302:9 | throw ... | 302:c3-c9 303:c3-c9 | -| test.cpp:304:3:304:9 | throw ... | 304:c3-c9 305:c3-c9 | -| test.cpp:306:3:306:7 | re-throw exception | 306:c3-c7 307:c3-c7 | -| test.cpp:311:3:311:3 | x | 311:c3-c3 312:c3-c3 313:c3-c3 314:c3-c3 | -| test.cpp:311:3:311:6 | access to array | 311:c3-c6 312:c3-c6 | -| test.cpp:311:5:311:5 | 0 | 311:c5-c5 312:c5-c5 315:c5-c5 44:c9-c9 51:c25-c25 88:c12-c12 | -| test.cpp:313:3:313:6 | access to array | 313:c3-c6 314:c3-c6 | -| test.cpp:315:3:315:3 | y | 315:c3-c3 316:c3-c3 | -| test.cpp:323:3:323:11 | test_18_p | 323:c3-c11 324:c3-c11 | -| test.cpp:323:3:323:13 | call to expression | 323:c3-c13 324:c3-c13 | -| test.cpp:327:3:327:11 | test_19_p | 327:c3-c11 328:c3-c11 329:c3-c11 | -| test.cpp:327:3:327:17 | call to expression | 327:c3-c17 328:c3-c17 | -| test.cpp:327:13:327:13 | x | 327:c13-c13 328:c13-c13 329:c16-c16 | -| test.cpp:327:16:327:16 | y | 327:c16-c16 328:c16-c16 329:c13-c13 | -| test.cpp:333:3:333:8 | ... == ... | 333:c3-c8 334:c3-c8 336:c3-c8 | -| test.cpp:333:3:333:16 | ... ? ... : ... | 333:c3-c16 334:c3-c16 | -| test.cpp:333:12:333:12 | x | 333:c12-c12 333:c3-c3 334:c12-c12 334:c3-c3 335:c12-c12 335:c8-c8 336:c16-c16 336:c3-c3 | -| test.cpp:333:16:333:16 | y | 333:c16-c16 333:c8-c8 334:c16-c16 334:c8-c8 335:c16-c16 335:c3-c3 336:c12-c12 336:c8-c8 | +| test.cpp:239:3:239:12 | new | 239:c3-c12 240:c3-c12 | +| test.cpp:245:16:245:36 | new[] | 245:c16-c36 246:c16-c36 | +| test.cpp:248:3:248:28 | delete | 248:c3-c28 249:c3-c28 | +| test.cpp:248:10:248:28 | | 248:c10-c28 249:c10-c28 250:c10-c28 252:c10-c32 253:c12-c34 254:c3-c25 255:c3-c25 257:c3-c19 258:c3-c19 260:c3-c23 261:c3-c23 | +| test.cpp:248:10:248:28 | call to operator new | 248:c10-c28 249:c10-c28 | +| test.cpp:248:10:248:28 | new | 248:c10-c28 249:c10-c28 | +| test.cpp:248:14:248:17 | (void *)... | 248:c14-c17 249:c14-c17 252:c18-c21 253:c20-c23 255:c11-c14 | +| test.cpp:248:14:248:17 | ptr1 | 248:c14-c17 249:c14-c17 252:c18-c21 253:c20-c23 255:c11-c14 | +| test.cpp:250:14:250:17 | (void *)... | 250:c14-c17 254:c11-c14 | +| test.cpp:250:14:250:17 | ptr2 | 250:c14-c17 254:c11-c14 | +| test.cpp:252:10:252:32 | call to operator new | 252:c10-c32 253:c12-c34 | +| test.cpp:252:10:252:32 | new | 252:c10-c32 253:c12-c34 | +| test.cpp:252:14:252:15 | 32 | 252:c14-c15 253:c16-c17 254:c7-c8 257:c7-c8 258:c7-c8 260:c7-c8 261:c7-c8 263:c16-c17 264:c16-c17 265:c16-c17 266:c7-c8 267:c7-c8 269:c7-c8 270:c7-c8 271:c7-c8 | +| test.cpp:252:14:252:15 | (size_t)... | 252:c14-c15 253:c16-c17 254:c7-c8 257:c7-c8 258:c7-c8 260:c7-c8 261:c7-c8 263:c16-c17 264:c16-c17 265:c16-c17 266:c7-c8 267:c7-c8 269:c7-c8 270:c7-c8 271:c7-c8 | +| test.cpp:257:3:257:19 | call to operator new | 257:c3-c19 258:c3-c19 | +| test.cpp:257:3:257:19 | new | 257:c3-c19 258:c3-c19 | +| test.cpp:260:3:260:23 | call to operator new[] | 260:c3-c23 261:c3-c23 | +| test.cpp:260:3:260:23 | new[] | 260:c3-c23 261:c3-c23 | +| test.cpp:260:21:260:22 | 10 | 260:c21-c22 261:c21-c22 92:c15-c16 | +| test.cpp:263:3:263:32 | delete[] | 263:c3-c32 264:c3-c32 | +| test.cpp:263:12:263:32 | | 263:c12-c32 264:c12-c32 265:c12-c32 266:c3-c23 267:c3-c23 269:c3-c19 270:c3-c19 271:c3-c19 | +| test.cpp:263:12:263:32 | call to operator new[] | 263:c12-c32 264:c12-c32 265:c12-c32 266:c3-c23 267:c3-c23 269:c3-c19 270:c3-c19 271:c3-c19 | +| test.cpp:263:12:263:32 | new[] | 263:c12-c32 264:c12-c32 | +| test.cpp:263:12:263:32 | {...} | 263:c12-c32 264:c12-c32 | +| test.cpp:265:28:265:28 | 3 | 265:c28-c28 271:c15-c15 35:c16-c16 | +| test.cpp:269:3:269:19 | new[] | 269:c3-c19 270:c3-c19 | +| test.cpp:269:3:269:19 | {...} | 269:c3-c19 270:c3-c19 | +| test.cpp:273:3:273:12 | new[] | 273:c3-c12 274:c3-c12 | +| test.cpp:273:11:273:11 | x | 273:c11-c11 274:c11-c11 | +| test.cpp:284:15:287:3 | {...} | 284:c15-c3 288:c15-c3 | +| test.cpp:299:3:299:9 | throw ... | 299:c3-c9 300:c3-c9 | +| test.cpp:301:3:301:9 | throw ... | 301:c3-c9 302:c3-c9 | +| test.cpp:303:3:303:7 | re-throw exception | 303:c3-c7 304:c3-c7 | +| test.cpp:308:3:308:3 | x | 308:c3-c3 309:c3-c3 310:c3-c3 311:c3-c3 | +| test.cpp:308:3:308:6 | access to array | 308:c3-c6 309:c3-c6 | +| test.cpp:308:5:308:5 | 0 | 308:c5-c5 309:c5-c5 312:c5-c5 44:c9-c9 51:c25-c25 88:c12-c12 | +| test.cpp:310:3:310:6 | access to array | 310:c3-c6 311:c3-c6 | +| test.cpp:312:3:312:3 | y | 312:c3-c3 313:c3-c3 | +| test.cpp:320:3:320:11 | test_18_p | 320:c3-c11 321:c3-c11 | +| test.cpp:320:3:320:13 | call to expression | 320:c3-c13 321:c3-c13 | +| test.cpp:324:3:324:11 | test_19_p | 324:c3-c11 325:c3-c11 326:c3-c11 | +| test.cpp:324:3:324:17 | call to expression | 324:c3-c17 325:c3-c17 | +| test.cpp:324:13:324:13 | x | 324:c13-c13 325:c13-c13 326:c16-c16 | +| test.cpp:324:16:324:16 | y | 324:c16-c16 325:c16-c16 326:c13-c13 | +| test.cpp:330:3:330:8 | ... == ... | 330:c3-c8 331:c3-c8 333:c3-c8 | +| test.cpp:330:3:330:16 | ... ? ... : ... | 330:c3-c16 331:c3-c16 | +| test.cpp:330:12:330:12 | x | 330:c12-c12 330:c3-c3 331:c12-c12 331:c3-c3 332:c12-c12 332:c8-c8 333:c16-c16 333:c3-c3 | +| test.cpp:330:16:330:16 | y | 330:c16-c16 330:c8-c8 331:c16-c16 331:c8-c8 332:c16-c16 332:c3-c3 333:c12-c12 333:c8-c8 | diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp index 40635ed268c..56190e8af2a 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/test.cpp @@ -211,10 +211,6 @@ int test15(int x) { alignof(x) + alignof(x); } -static void *operator new(size_t size) { - return malloc(size); -} - static void *operator new(size_t size, void *placement) { return placement; } @@ -227,10 +223,6 @@ static void *operator new(size_t size, size_t alignment) { return malloc(size); } -static void *operator new[](size_t size) { - return malloc(size); -} - static void *operator new[](size_t size, void *placement) { return placement; } @@ -243,7 +235,7 @@ static void *operator new[](size_t size, size_t alignment) { return malloc(size); } -void test16() { +void test16(int y, int z) { new int(1); new int(1); new int(2); @@ -276,6 +268,11 @@ void test16() { new(32) int[2] {}; new(32) int[2] {}; + new(32) int[3] {}; + + new int[x]; + new int[x]; + new int[z]; } typedef struct point{ From 2d098fed98906ecfbe7dde5ad5ebfc3e0c30fcbf Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 30 Aug 2018 15:02:57 -0700 Subject: [PATCH 32/73] fix HashCons for typeid of type --- cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index e3ac972a569..6aefb22f510 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -704,7 +704,7 @@ private predicate mk_UuidofOperator(Type t, UuidofOperator e) { } private predicate analyzableTypeidType(TypeidOperator e) { - strictcount(e.getAChild()) = 0 + count(e.getAChild()) = 0 } private predicate mk_TypeidType(Type t, TypeidOperator e) { From bbafcd99414a3ac0494eae6e719f636a53222925 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 31 Aug 2018 08:34:43 -0700 Subject: [PATCH 33/73] C++: typeid and noexcept fixes in HashCons --- cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 6aefb22f510..5494d656ac3 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -704,7 +704,8 @@ private predicate mk_UuidofOperator(Type t, UuidofOperator e) { } private predicate analyzableTypeidType(TypeidOperator e) { - count(e.getAChild()) = 0 + count(e.getAChild()) = 0 and + strictcount(e.getResultType()) = 1 } private predicate mk_TypeidType(Type t, TypeidOperator e) { @@ -858,7 +859,7 @@ private predicate analyzableNoExceptExpr(NoExceptExpr nee) { private predicate mk_NoExceptExpr(HashCons child, NoExceptExpr nee) { analyzableNoExceptExpr(nee) and - nee.getExpr() = child.getAnExpr().getFullyConverted() + nee.getExpr().getFullyConverted() = child.getAnExpr() } From 166dba288b6c79a44ca741f93cd545a1f945dcc1 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 4 Sep 2018 12:26:05 -0700 Subject: [PATCH 34/73] C++: accept test output --- cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll | 7 +++---- .../valuenumbering/HashCons/HashCons.expected | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 5494d656ac3..407ad5ff721 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -748,14 +748,13 @@ private predicate mk_FieldCons(Class c, int i, Field f, HashCons hc, HC_Fields h cal.getType().getUnspecifiedType() = c and exists(Expr e | e = cal.getFieldExpr(f).getFullyConverted() and - e = cal.getChild(i).getFullyConverted() and + f.getInitializationOrder() = i and hc = hashCons(e) and ( exists(HashCons head, Field f2, HC_Fields tail | hcf = HC_FieldCons(c, i-1, f2, head, tail) and - cal.getChild(i-1).getFullyConverted() = cal.getFieldExpr(f2).getFullyConverted() and + f2.getInitializationOrder() = i-1 and mk_FieldCons(c, i-1, f2, head, tail, cal) - ) or i = 0 and @@ -777,7 +776,7 @@ private predicate mk_ClassAggregateLiteral(Class c, HC_Fields hcf, ClassAggregat c = cal.getType().getUnspecifiedType() and ( exists(HC_Fields tail, Expr e, Field f | - e = cal.getChild(cal.getNumChild() - 1).getFullyConverted() and + f.getInitializationOrder() = cal.getNumChild() - 1 and e = cal.getFieldExpr(f).getFullyConverted() and hcf = HC_FieldCons(c, cal.getNumChild() - 1, f, hashCons(e), tail) and mk_FieldCons(c, cal.getNumChild() - 1, f, hashCons(e), tail, cal) diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 48adfedf87e..0f92bbd658e 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -111,7 +111,6 @@ | test.cpp:269:3:269:19 | {...} | 269:c3-c19 270:c3-c19 | | test.cpp:273:3:273:12 | new[] | 273:c3-c12 274:c3-c12 | | test.cpp:273:11:273:11 | x | 273:c11-c11 274:c11-c11 | -| test.cpp:284:15:287:3 | {...} | 284:c15-c3 288:c15-c3 | | test.cpp:299:3:299:9 | throw ... | 299:c3-c9 300:c3-c9 | | test.cpp:301:3:301:9 | throw ... | 301:c3-c9 302:c3-c9 | | test.cpp:303:3:303:7 | re-throw exception | 303:c3-c7 304:c3-c7 | From 990bfb4663b7deaf5b45cb61a8aacfea96587968 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 4 Sep 2018 13:47:42 -0700 Subject: [PATCH 35/73] C++: change note for HashCons library --- change-notes/1.18/analysis-cpp.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/change-notes/1.18/analysis-cpp.md b/change-notes/1.18/analysis-cpp.md index 00877a12462..7f3f2845387 100644 --- a/change-notes/1.18/analysis-cpp.md +++ b/change-notes/1.18/analysis-cpp.md @@ -31,10 +31,11 @@ | [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. | - + ## 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`. +* Added a hash consing library for structural comparison of expressions. From fb8ad9387d5fe3aad8f2b4561f5a0d987fff23cc Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 6 Sep 2018 12:32:37 -0700 Subject: [PATCH 36/73] C++: Uniqueness fixes for HashCons --- .../code/cpp/valuenumbering/HashCons.qll | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 407ad5ff721..7f468aa2da5 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -255,7 +255,7 @@ class HashCons extends HCBase { if this instanceof HC_TypeidType then result = "TypeidType" else if this instanceof HC_TypeidExpr then result = "TypeidExpr" else if this instanceof HC_ArrayAggregateLiteral then result = "ArrayAggregateLiteral" else - if this instanceof HC_ClassAggregateLiteral then result = "ClassAggreagateLiteral" else + if this instanceof HC_ClassAggregateLiteral then result = "ClassAggregateLiteral" else if this instanceof HC_DeleteExpr then result = "DeleteExpr" else if this instanceof HC_DeleteArrayExpr then result = "DeleteArrayExpr" else if this instanceof HC_ThrowExpr then result = "ThrowExpr" else @@ -464,7 +464,7 @@ private predicate mk_Deref(HashCons p, PointerDereferenceExpr deref) { private predicate analyzableNonmemberFunctionCall(FunctionCall fc) { forall(int i | - exists(fc.getArgument(i)) | + i in [0..fc.getNumberOfArguments()-1] | strictcount(fc.getArgument(i).getFullyConverted()) = 1 ) and strictcount(fc.getTarget()) = 1 and @@ -487,7 +487,7 @@ private predicate mk_NonmemberFunctionCall(Function fcn, HC_Args args, FunctionC private predicate analyzableExprCall(ExprCall ec) { forall(int i | - exists(ec.getArgument(i)) | + i in [0..ec.getNumberOfArguments()-1] | strictcount(ec.getArgument(i).getFullyConverted()) = 1 ) and strictcount(ec.getExpr().getFullyConverted()) = 1 @@ -508,7 +508,7 @@ private predicate mk_ExprCall(HashCons hc, HC_Args args, ExprCall ec) { private predicate analyzableMemberFunctionCall( FunctionCall fc) { forall(int i | - exists(fc.getArgument(i)) | + i in [0..fc.getNumberOfArguments()-1] | strictcount(fc.getArgument(i).getFullyConverted()) = 1 ) and strictcount(fc.getTarget()) = 1 and @@ -573,11 +573,15 @@ private predicate mk_ArgConsInner(HashCons head, HC_Args tail, int i, HC_Args li */ private predicate analyzableAllocatorArgZero(ErrorExpr e) { exists(NewOrNewArrayExpr new | - new.getAllocatorCall().getChild(0) = e + new.getAllocatorCall().getChild(0) = e and + strictcount(new.getType().getUnspecifiedType()) = 1 ) + and + strictcount(NewOrNewArrayExpr new | new.getAllocatorCall().getChild(0) = e) = 1 } private predicate mk_AllocatorArgZero(Type t, ErrorExpr e) { + analyzableAllocatorArgZero(e) and exists(NewOrNewArrayExpr new | new.getAllocatorCall().getChild(0) = e and t = new.getType().getUnspecifiedType() @@ -767,7 +771,9 @@ private predicate analyzableClassAggregateLiteral(ClassAggregateLiteral cal) { forall(int i | exists(cal.getChild(i)) | strictcount(cal.getChild(i).getFullyConverted()) = 1 and - strictcount(Field f | cal.getChild(i) = cal.getFieldExpr(f)) = 1 + strictcount(Field f | cal.getChild(i) = cal.getFieldExpr(f)) = 1 and + strictcount(Field f, int j | + cal.getFieldExpr(f) = cal.getChild(i) and j = f.getInitializationOrder()) = 1 ) } @@ -791,10 +797,12 @@ private predicate analyzableArrayAggregateLiteral(ArrayAggregateLiteral aal) { forall(int i | exists(aal.getChild(i)) | strictcount(aal.getChild(i).getFullyConverted()) = 1 - ) + ) and + strictcount(aal.getType().getUnspecifiedType()) = 1 } private predicate mk_ArrayCons(Type t, int i, HashCons hc, HC_Array hca, ArrayAggregateLiteral aal) { + analyzableArrayAggregateLiteral(aal) and t = aal.getType().getUnspecifiedType() and hc = hashCons(aal.getChild(i)) and ( @@ -935,7 +943,7 @@ cached HashCons hashCons(Expr e) { result = HC_MemberFunctionCall(fcn, qual, args) ) or - // works around an extractor issue class + // works around an extractor issue exists(Type t | mk_AllocatorArgZero(t, e) and result = HC_AllocatorArgZero(t) @@ -972,6 +980,11 @@ cached HashCons hashCons(Expr e) { ) or exists(Type t + | mk_UuidofOperator(t, e) and + result = HC_UuidofOperator(t) + ) + or + exists(Type t | mk_AlignofType(t, e) and result = HC_AlignofType(t) ) From cb9f1269f938eaab6ccaf73a105f4b3123c3d050 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 6 Sep 2018 14:44:02 -0700 Subject: [PATCH 37/73] C++: select example exprs for HashCons portably This makes two changes to how example exprs are selected. Example exprs are now ordered separately by each piece of the location, rather than by stringifying their location. Second, UnknownLocations are now ordered after locations with absolute paths, by using "~" in the lexicographic comparison of absolute paths. I think this works on both POSIX and Windows systems, but it's possible I'm missing a way to start an absolute path with a unicode character. --- .../code/cpp/valuenumbering/HashCons.qll | 14 +++- .../valuenumbering/HashCons/HashCons.expected | 74 +++++++++---------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll index 7f468aa2da5..07923d60b03 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/HashCons.qll @@ -277,7 +277,8 @@ class HashCons extends HCBase { result = min(Expr e | this = hashCons(e) - | e order by e.getLocation().toString()) + | e order by exampleLocationString(e.getLocation()), e.getLocation().getStartColumn(), + e.getLocation().getEndLine(), e.getLocation().getEndColumn()) } /** Gets a textual representation of this element. */ @@ -291,6 +292,17 @@ class HashCons extends HCBase { } } +/** + * Gets the absolute path of a known location or "~" for an unknown location. This ensures that + * expressions with unknown locations are ordered after expressions with known locations when + * selecting an example expression for a HashCons value. + */ +private string exampleLocationString(Location l) { + if l instanceof UnknownLocation + then result = "~" + else result = l.getFile().getAbsolutePath() +} + private predicate analyzableIntLiteral(Literal e) { strictcount (e.getValue().toInt()) = 1 and strictcount (e.getType().getUnspecifiedType()) = 1 and diff --git a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected index 0f92bbd658e..c050ac2bffd 100644 --- a/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected +++ b/cpp/ql/test/library-tests/valuenumbering/HashCons/HashCons.expected @@ -1,4 +1,3 @@ -| file://:0:0:0:0 | this | 0:c0-c0 141:c23-c26 | | test.cpp:5:3:5:3 | x | 5:c3-c3 6:c3-c3 7:c7-c7 | | test.cpp:5:7:5:8 | p0 | 5:c7-c8 6:c7-c8 | | test.cpp:5:7:5:13 | ... + ... | 5:c7-c13 6:c7-c13 | @@ -24,48 +23,48 @@ | test.cpp:53:10:53:13 | (int)... | 53:c10-c13 56:c21-c24 | | test.cpp:53:10:53:13 | * ... | 53:c10-c13 56:c21-c24 | | test.cpp:53:11:53:13 | str | 53:c11-c13 56:c22-c24 | -| test.cpp:53:18:53:21 | 0 | 53:c18-c21 56:c39-c42 59:c17-c20 | -| test.cpp:53:18:53:21 | (int)... | 53:c18-c21 56:c39-c42 59:c17-c20 | | test.cpp:55:5:55:7 | ptr | 55:c5-c7 56:c14-c16 56:c32-c34 56:c47-c49 59:c10-c12 | -| test.cpp:56:13:56:16 | (int)... | 56:c13-c16 56:c31-c34 59:c9-c12 | -| test.cpp:56:13:56:16 | * ... | 56:c13-c16 56:c31-c34 59:c9-c12 | +| test.cpp:59:9:59:12 | (int)... | 56:c13-c16 56:c31-c34 59:c9-c12 | +| test.cpp:59:9:59:12 | * ... | 56:c13-c16 56:c31-c34 59:c9-c12 | +| test.cpp:59:17:59:20 | 0 | 53:c18-c21 56:c39-c42 59:c17-c20 | +| test.cpp:59:17:59:20 | (int)... | 53:c18-c21 56:c39-c42 59:c17-c20 | | test.cpp:62:5:62:10 | result | 62:c5-c10 65:c10-c15 | -| test.cpp:77:20:77:28 | call to getAValue | 77:c20-c28 80:c9-c17 | -| test.cpp:77:20:77:30 | (signed short)... | 77:c20-c30 80:c9-c19 | -| test.cpp:79:7:79:7 | v | 79:c7-c7 80:c5-c5 | | test.cpp:79:11:79:14 | vals | 79:c11-c14 79:c24-c27 | -| test.cpp:92:11:92:11 | x | 92:c11-c11 93:c10-c10 | +| test.cpp:80:5:80:5 | v | 79:c7-c7 80:c5-c5 | +| test.cpp:80:9:80:17 | call to getAValue | 77:c20-c28 80:c9-c17 | +| test.cpp:80:9:80:19 | (signed short)... | 77:c20-c30 80:c9-c19 | +| test.cpp:92:15:92:16 | 10 | 260:c21-c22 261:c21-c22 92:c15-c16 | +| test.cpp:93:10:93:10 | x | 92:c11-c11 93:c10-c10 | | test.cpp:97:3:97:3 | x | 97:c3-c3 98:c3-c3 | | test.cpp:97:3:97:5 | ... ++ | 97:c3-c5 98:c3-c5 | -| test.cpp:103:10:103:11 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 239:c11-c11 240:c11-c11 263:c28-c28 264:c28-c28 266:c19-c19 266:c22-c22 285:c5-c5 289:c5-c5 294:c5-c5 299:c9-c9 300:c9-c9 310:c5-c5 311:c5-c5 313:c5-c5 | | test.cpp:104:3:104:3 | x | 104:c3-c3 105:c3-c3 106:c3-c3 107:c3-c3 108:c3-c3 | -| test.cpp:105:7:105:7 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 241:c11-c11 263:c24-c24 263:c31-c31 264:c24-c24 264:c31-c31 265:c24-c24 266:c15-c15 267:c15-c15 267:c19-c19 267:c22-c22 269:c15-c15 270:c15-c15 286:c5-c5 290:c5-c5 293:c5-c5 301:c9-c9 302:c9-c9 | | test.cpp:107:7:107:11 | ... + ... | 107:c7-c11 108:c7-c11 | -| test.cpp:110:15:110:17 | 1 | 110:c15-c17 111:c9-c11 | -| test.cpp:110:15:110:17 | (char *)... | 110:c15-c17 111:c9-c11 | -| test.cpp:110:15:110:17 | array to pointer conversion | 110:c15-c17 111:c9-c11 | | test.cpp:111:3:111:5 | str | 111:c3-c5 112:c3-c5 113:c3-c5 | +| test.cpp:111:9:111:11 | 1 | 110:c15-c17 111:c9-c11 | +| test.cpp:111:9:111:11 | (char *)... | 110:c15-c17 111:c9-c11 | +| test.cpp:111:9:111:11 | array to pointer conversion | 110:c15-c17 111:c9-c11 | | test.cpp:112:9:112:11 | 2 | 112:c9-c11 113:c9-c11 | | test.cpp:112:9:112:11 | (char *)... | 112:c9-c11 113:c9-c11 | | test.cpp:112:9:112:11 | array to pointer conversion | 112:c9-c11 113:c9-c11 | -| test.cpp:115:13:115:15 | 0.0 | 115:c13-c15 116:c7-c9 | -| test.cpp:115:13:115:15 | (float)... | 115:c13-c15 116:c7-c9 | | test.cpp:116:3:116:3 | y | 116:c3-c3 117:c3-c3 118:c3-c3 | +| test.cpp:116:7:116:9 | 0.0 | 115:c13-c15 116:c7-c9 | +| test.cpp:116:7:116:9 | (float)... | 115:c13-c15 116:c7-c9 | | test.cpp:117:7:117:9 | 0.5 | 117:c7-c9 118:c7-c9 | | test.cpp:117:7:117:9 | (float)... | 117:c7-c9 118:c7-c9 | | test.cpp:122:3:122:8 | call to test07 | 122:c3-c8 123:c3-c8 | | test.cpp:125:3:125:11 | call to my_strspn | 125:c3-c11 126:c3-c11 | | test.cpp:125:13:125:17 | array to pointer conversion | 125:c13-c17 126:c13-c17 129:c20-c24 | | test.cpp:125:13:125:17 | foo | 125:c13-c17 126:c13-c17 129:c20-c24 | -| test.cpp:125:20:125:24 | array to pointer conversion | 125:c20-c24 126:c20-c24 129:c13-c17 | -| test.cpp:125:20:125:24 | bar | 125:c20-c24 126:c20-c24 129:c13-c17 | +| test.cpp:129:13:129:17 | array to pointer conversion | 125:c20-c24 126:c20-c24 129:c13-c17 | +| test.cpp:129:13:129:17 | bar | 125:c20-c24 126:c20-c24 129:c13-c17 | | test.cpp:141:12:141:17 | call to getInt | 141:c12-c17 141:c29-c34 | +| test.cpp:141:23:141:26 | this | 0:c0-c0 141:c23-c26 | | test.cpp:146:10:146:11 | ih | 146:c10-c11 146:c31-c32 | | test.cpp:146:13:146:25 | call to getDoubledInt | 146:c13-c25 146:c34-c46 | | test.cpp:150:3:150:3 | x | 150:c3-c3 150:c9-c9 151:c3-c3 151:c9-c9 152:c12-c12 | | test.cpp:150:3:150:5 | ... ++ | 150:c3-c5 150:c9-c11 151:c3-c5 151:c9-c11 152:c10-c12 | | test.cpp:150:3:150:11 | ... + ... | 150:c3-c11 151:c3-c11 | -| test.cpp:156:14:156:20 | 0 | 156:c14-c20 156:c3-c9 157:c10-c16 | +| test.cpp:156:3:156:9 | 0 | 156:c14-c20 156:c3-c9 157:c10-c16 | | test.cpp:171:3:171:6 | (int)... | 171:c3-c6 172:c3-c6 | | test.cpp:171:3:171:6 | e1x1 | 171:c3-c6 172:c3-c6 | | test.cpp:179:10:179:22 | (...) | 179:c10-c22 179:c10-c22 | @@ -74,43 +73,44 @@ | test.cpp:185:17:185:17 | y | 185:c17-c17 185:c17-c17 | | test.cpp:202:3:202:18 | sizeof(padded_t) | 202:c3-c18 204:c3-c18 | | test.cpp:205:24:205:34 | sizeof(int) | 205:c24-c34 245:c25-c35 246:c25-c35 | -| test.cpp:206:25:206:43 | alignof(int_holder) | 206:c25-c43 206:c3-c21 | -| test.cpp:208:27:208:27 | x | 208:c27-c27 210:c10-c10 211:c11-c11 211:c24-c24 | +| test.cpp:206:3:206:21 | alignof(int_holder) | 206:c25-c43 206:c3-c21 | +| test.cpp:209:3:209:18 | sizeof() | 209:c22-c37 209:c3-c18 | | test.cpp:209:10:209:15 | holder | 209:c10-c15 209:c29-c34 | | test.cpp:209:10:209:18 | (...) | 209:c10-c18 209:c29-c37 | | test.cpp:209:17:209:17 | x | 209:c17-c17 209:c36-c36 | -| test.cpp:209:22:209:37 | sizeof() | 209:c22-c37 209:c3-c18 | +| test.cpp:210:10:210:10 | x | 208:c27-c27 210:c10-c10 211:c11-c11 211:c24-c24 | | test.cpp:210:10:210:11 | (...) | 210:c10-c11 211:c11-c12 211:c24-c25 | -| test.cpp:211:16:211:25 | alignof() | 211:c16-c25 211:c3-c12 | +| test.cpp:211:3:211:12 | alignof() | 211:c16-c25 211:c3-c12 | | test.cpp:239:3:239:12 | new | 239:c3-c12 240:c3-c12 | | test.cpp:245:16:245:36 | new[] | 245:c16-c36 246:c16-c36 | | test.cpp:248:3:248:28 | delete | 248:c3-c28 249:c3-c28 | -| test.cpp:248:10:248:28 | | 248:c10-c28 249:c10-c28 250:c10-c28 252:c10-c32 253:c12-c34 254:c3-c25 255:c3-c25 257:c3-c19 258:c3-c19 260:c3-c23 261:c3-c23 | | test.cpp:248:10:248:28 | call to operator new | 248:c10-c28 249:c10-c28 | | test.cpp:248:10:248:28 | new | 248:c10-c28 249:c10-c28 | -| test.cpp:248:14:248:17 | (void *)... | 248:c14-c17 249:c14-c17 252:c18-c21 253:c20-c23 255:c11-c14 | -| test.cpp:248:14:248:17 | ptr1 | 248:c14-c17 249:c14-c17 252:c18-c21 253:c20-c23 255:c11-c14 | -| test.cpp:250:14:250:17 | (void *)... | 250:c14-c17 254:c11-c14 | -| test.cpp:250:14:250:17 | ptr2 | 250:c14-c17 254:c11-c14 | | test.cpp:252:10:252:32 | call to operator new | 252:c10-c32 253:c12-c34 | | test.cpp:252:10:252:32 | new | 252:c10-c32 253:c12-c34 | -| test.cpp:252:14:252:15 | 32 | 252:c14-c15 253:c16-c17 254:c7-c8 257:c7-c8 258:c7-c8 260:c7-c8 261:c7-c8 263:c16-c17 264:c16-c17 265:c16-c17 266:c7-c8 267:c7-c8 269:c7-c8 270:c7-c8 271:c7-c8 | -| test.cpp:252:14:252:15 | (size_t)... | 252:c14-c15 253:c16-c17 254:c7-c8 257:c7-c8 258:c7-c8 260:c7-c8 261:c7-c8 263:c16-c17 264:c16-c17 265:c16-c17 266:c7-c8 267:c7-c8 269:c7-c8 270:c7-c8 271:c7-c8 | +| test.cpp:254:3:254:25 | | 248:c10-c28 249:c10-c28 250:c10-c28 252:c10-c32 253:c12-c34 254:c3-c25 255:c3-c25 257:c3-c19 258:c3-c19 260:c3-c23 261:c3-c23 | +| test.cpp:254:7:254:8 | 32 | 252:c14-c15 253:c16-c17 254:c7-c8 257:c7-c8 258:c7-c8 260:c7-c8 261:c7-c8 263:c16-c17 264:c16-c17 265:c16-c17 266:c7-c8 267:c7-c8 269:c7-c8 270:c7-c8 271:c7-c8 | +| test.cpp:254:7:254:8 | (size_t)... | 252:c14-c15 253:c16-c17 254:c7-c8 257:c7-c8 258:c7-c8 260:c7-c8 261:c7-c8 263:c16-c17 264:c16-c17 265:c16-c17 266:c7-c8 267:c7-c8 269:c7-c8 270:c7-c8 271:c7-c8 | +| test.cpp:254:11:254:14 | (void *)... | 250:c14-c17 254:c11-c14 | +| test.cpp:254:11:254:14 | ptr2 | 250:c14-c17 254:c11-c14 | +| test.cpp:255:11:255:14 | (void *)... | 248:c14-c17 249:c14-c17 252:c18-c21 253:c20-c23 255:c11-c14 | +| test.cpp:255:11:255:14 | ptr1 | 248:c14-c17 249:c14-c17 252:c18-c21 253:c20-c23 255:c11-c14 | | test.cpp:257:3:257:19 | call to operator new | 257:c3-c19 258:c3-c19 | | test.cpp:257:3:257:19 | new | 257:c3-c19 258:c3-c19 | | test.cpp:260:3:260:23 | call to operator new[] | 260:c3-c23 261:c3-c23 | | test.cpp:260:3:260:23 | new[] | 260:c3-c23 261:c3-c23 | -| test.cpp:260:21:260:22 | 10 | 260:c21-c22 261:c21-c22 92:c15-c16 | | test.cpp:263:3:263:32 | delete[] | 263:c3-c32 264:c3-c32 | -| test.cpp:263:12:263:32 | | 263:c12-c32 264:c12-c32 265:c12-c32 266:c3-c23 267:c3-c23 269:c3-c19 270:c3-c19 271:c3-c19 | -| test.cpp:263:12:263:32 | call to operator new[] | 263:c12-c32 264:c12-c32 265:c12-c32 266:c3-c23 267:c3-c23 269:c3-c19 270:c3-c19 271:c3-c19 | | test.cpp:263:12:263:32 | new[] | 263:c12-c32 264:c12-c32 | | test.cpp:263:12:263:32 | {...} | 263:c12-c32 264:c12-c32 | -| test.cpp:265:28:265:28 | 3 | 265:c28-c28 271:c15-c15 35:c16-c16 | +| test.cpp:266:3:266:23 | | 263:c12-c32 264:c12-c32 265:c12-c32 266:c3-c23 267:c3-c23 269:c3-c19 270:c3-c19 271:c3-c19 | +| test.cpp:266:3:266:23 | call to operator new[] | 263:c12-c32 264:c12-c32 265:c12-c32 266:c3-c23 267:c3-c23 269:c3-c19 270:c3-c19 271:c3-c19 | | test.cpp:269:3:269:19 | new[] | 269:c3-c19 270:c3-c19 | | test.cpp:269:3:269:19 | {...} | 269:c3-c19 270:c3-c19 | +| test.cpp:271:15:271:15 | 3 | 265:c28-c28 271:c15-c15 35:c16-c16 | | test.cpp:273:3:273:12 | new[] | 273:c3-c12 274:c3-c12 | | test.cpp:273:11:273:11 | x | 273:c11-c11 274:c11-c11 | +| test.cpp:285:5:285:5 | 1 | 103:c10-c11 104:c7-c7 107:c7-c7 108:c7-c7 10:c16-c16 179:c21-c21 239:c11-c11 240:c11-c11 263:c28-c28 264:c28-c28 266:c19-c19 266:c22-c22 285:c5-c5 289:c5-c5 294:c5-c5 299:c9-c9 300:c9-c9 310:c5-c5 311:c5-c5 313:c5-c5 | +| test.cpp:286:5:286:5 | 2 | 105:c7-c7 106:c7-c7 107:c11-c11 108:c11-c11 21:c16-c16 241:c11-c11 263:c24-c24 263:c31-c31 264:c24-c24 264:c31-c31 265:c24-c24 266:c15-c15 267:c15-c15 267:c19-c19 267:c22-c22 269:c15-c15 270:c15-c15 286:c5-c5 290:c5-c5 293:c5-c5 301:c9-c9 302:c9-c9 | | test.cpp:299:3:299:9 | throw ... | 299:c3-c9 300:c3-c9 | | test.cpp:301:3:301:9 | throw ... | 301:c3-c9 302:c3-c9 | | test.cpp:303:3:303:7 | re-throw exception | 303:c3-c7 304:c3-c7 | @@ -124,8 +124,8 @@ | test.cpp:324:3:324:11 | test_19_p | 324:c3-c11 325:c3-c11 326:c3-c11 | | test.cpp:324:3:324:17 | call to expression | 324:c3-c17 325:c3-c17 | | test.cpp:324:13:324:13 | x | 324:c13-c13 325:c13-c13 326:c16-c16 | -| test.cpp:324:16:324:16 | y | 324:c16-c16 325:c16-c16 326:c13-c13 | +| test.cpp:326:13:326:13 | y | 324:c16-c16 325:c16-c16 326:c13-c13 | +| test.cpp:330:3:330:3 | x | 330:c12-c12 330:c3-c3 331:c12-c12 331:c3-c3 332:c12-c12 332:c8-c8 333:c16-c16 333:c3-c3 | | test.cpp:330:3:330:8 | ... == ... | 330:c3-c8 331:c3-c8 333:c3-c8 | | test.cpp:330:3:330:16 | ... ? ... : ... | 330:c3-c16 331:c3-c16 | -| test.cpp:330:12:330:12 | x | 330:c12-c12 330:c3-c3 331:c12-c12 331:c3-c3 332:c12-c12 332:c8-c8 333:c16-c16 333:c3-c3 | -| test.cpp:330:16:330:16 | y | 330:c16-c16 330:c8-c8 331:c16-c16 331:c8-c8 332:c16-c16 332:c3-c3 333:c12-c12 333:c8-c8 | +| test.cpp:332:3:332:3 | y | 330:c16-c16 330:c8-c8 331:c16-c16 331:c8-c8 332:c16-c16 332:c3-c3 333:c12-c12 333:c8-c8 | From 0e44bf3c30432ac0dd91b364db5591014c6c0b22 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 10 Sep 2018 12:22:19 -0700 Subject: [PATCH 38/73] C++: Add import for LGTM --- cpp/ql/src/filters/ImportAdditionalLibraries.ql | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/ql/src/filters/ImportAdditionalLibraries.ql b/cpp/ql/src/filters/ImportAdditionalLibraries.ql index 1d54da5f6e7..261ee3de0ca 100644 --- a/cpp/ql/src/filters/ImportAdditionalLibraries.ql +++ b/cpp/ql/src/filters/ImportAdditionalLibraries.ql @@ -14,6 +14,7 @@ import semmle.code.cpp.dataflow.DataFlow2 import semmle.code.cpp.dataflow.DataFlow3 import semmle.code.cpp.dataflow.DataFlow4 import semmle.code.cpp.dataflow.TaintTracking +import semmle.code.cpp.valuenumbering.HashCons from File f, string tag where none() From befca6cafacdbd8c8af789a7fb6e86ee55cbd2f9 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Tue, 11 Sep 2018 12:31:00 -0700 Subject: [PATCH 39/73] Remove webview example and its reference in qlhelp file --- .../Electron/EnablingNodeIntegration.qhelp | 19 ++++++------------- .../examples/WebViewNodeIntegration.html | 15 --------------- 2 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 javascript/ql/src/Electron/examples/WebViewNodeIntegration.html diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp index 47337a03b4e..c6a9af938d7 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp @@ -5,10 +5,10 @@

    - Enabling Node.js integration in web content renderers (BrowserWindow, BrowserView and webview) could result in + Enabling Node.js integration in web content renderers (BrowserWindow, BrowserView and webview) could result in remote native code execution attacks when rendering malicious JavaScript code from untrusted remote web site or - code that is injected via a cross site scripting vulnerability into the web content under processing. Please note that - the nodeIntegration property is enabled by default in Electron and needs to be set to 'false' explicitly. + code that is injected via a cross site scripting vulnerability into a trusted remote web site. Note that + the nodeIntegration property is enabled by default in Electron and needs to be set to false explicitly.

    @@ -21,28 +21,21 @@

    - The following example shows insecure use of BrowserWindow with regards to nodeIntegration + The following example shows insecure use of BrowserWindow with regards to nodeIntegration property:

    - This is problematic, because default value of nodeIntegration is 'true'. + This is problematic, because default value of nodeIntegration is 'true'.

    - -

    - The following example shows insecure and secure uses of webview tag: -

    - - -

    - The following example shows insecure and secure uses of BrowserWindow and BrowserView when + The following example shows insecure and secure uses of BrowserWindow and BrowserView when loading untrusted web sites:

    diff --git a/javascript/ql/src/Electron/examples/WebViewNodeIntegration.html b/javascript/ql/src/Electron/examples/WebViewNodeIntegration.html deleted file mode 100644 index 1deaa6de332..00000000000 --- a/javascript/ql/src/Electron/examples/WebViewNodeIntegration.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - WebView Examples - - - - - - - - - - \ No newline at end of file From fc087ffb71c6ef5bd3a264db845b0fdab68db410 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Tue, 11 Sep 2018 12:32:56 -0700 Subject: [PATCH 40/73] Replaceing query and test files with suggested ones --- .../src/Electron/EnablingNodeIntegration.ql | 36 ++++++++++--------- .../EnablingNodeIntegration.expected | 10 +++--- .../EnablingNodeIntegration.js | 17 ++++----- .../EnablingNodeIntegration.qlref | 2 +- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.ql b/javascript/ql/src/Electron/EnablingNodeIntegration.ql index 39b19c5d43b..f38d854c1dc 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.ql +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.ql @@ -1,28 +1,32 @@ /** - * @name Enabling nodeIntegration and nodeIntegrationInWorker in webPreferences - * @description Enabling nodeIntegration and nodeIntegrationInWorker could expose your app to remote code execution. + * @name Enabling `nodeIntegration` or `nodeIntegrationInWorker` for Electron web content + * @description Enabling `nodeIntegration` or `nodeIntegrationInWorker` can expose the application to remote code execution. * @kind problem * @problem.severity warning - * @precision very-high + * @id js/enabling-electron-renderer-node-integration * @tags security * frameworks/electron - * @id js/enabling-electron-renderer-node-integration */ import javascript -string checkWebOptions(DataFlow::PropWrite prop, Electron::WebPreferences pref) { - (prop = pref.getAPropertyWrite("nodeIntegration") and - prop.getRhs().mayHaveBooleanValue(true) and - result = "nodeIntegration property may have been enabled on this object that could result in RCE") +/** + * Gets a warning message for `pref` if one of the `nodeIntegration` features is enabled. + */ +string getNodeIntegrationWarning(Electron::WebPreferences pref) { + exists (string feature | + feature = "nodeIntegration" or + feature = "nodeIntegrationInWorker" | + pref.getAPropertyWrite(feature).getRhs().mayHaveBooleanValue(true) and + result = "The `" + feature + "` feature has been enabled." + ) or - (prop = pref.getAPropertyWrite("nodeIntegrationInWorker") and - prop.getRhs().mayHaveBooleanValue(true) and - result = "nodeIntegrationInWorker property may have been enabled on this object that could result in RCE") - or - (not exists(pref.asExpr().(ObjectExpr).getPropertyByName("nodeIntegration")) and - result = "nodeIntegration is enabled by default in WebPreferences object that could result in RCE") + exists (string feature | + feature = "nodeIntegration" | + not exists(pref.getAPropertyWrite(feature)) and + result = "The `" + feature + "` feature is enabled by default." + ) } -from DataFlow::PropWrite property, Electron::WebPreferences preferences -select preferences,checkWebOptions(property, preferences) \ No newline at end of file +from Electron::WebPreferences preferences +select preferences, getNodeIntegrationWarning(preferences) \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected index 092ad577de3..674fd74caee 100644 --- a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.expected @@ -1,5 +1,5 @@ -| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegration property may have been enabled on this object that could result in RCE | -| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | nodeIntegrationInWorker property may have been enabled on this object that could result in RCE | -| EnablingNodeIntegration.js:15:22:20:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | -| EnablingNodeIntegration.js:23:13:27:9 | {\\n ... } | nodeIntegration is enabled by default in WebPreferences object that could result in RCE | -| EnablingNodeIntegration.js:49:71:49:93 | {nodeIn ... : true} | nodeIntegration property may have been enabled on this object that could result in RCE | +| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | The `nodeIntegrationInWorker` feature has been enabled. | +| EnablingNodeIntegration.js:5:28:11:9 | {\\n ... } | The `nodeIntegration` feature has been enabled. | +| EnablingNodeIntegration.js:15:22:20:9 | {\\n ... } | The `nodeIntegration` feature is enabled by default. | +| EnablingNodeIntegration.js:23:16:27:9 | { // NO ... } | The `nodeIntegration` feature is enabled by default. | +| EnablingNodeIntegration.js:49:74:49:96 | {nodeIn ... : true} | The `nodeIntegration` feature has been enabled. | diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js index fd1a4201df7..5e1d0e95fb4 100644 --- a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.js @@ -1,7 +1,7 @@ const {BrowserWindow} = require('electron') function test() { - var unsafe_1 = { + var unsafe_1 = { // NOT OK, both enabled webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true, @@ -11,7 +11,7 @@ function test() { } }; - var options_1 = { + var options_1 = { // NOT OK, `nodeIntegrationInWorker` enabled webPreferences: { plugins: true, nodeIntegrationInWorker: false, @@ -20,13 +20,13 @@ function test() { } }; - var pref = { + var pref = { // NOT OK, implicitly enabled plugins: true, webSecurity: true, sandbox: true }; - var options_2 = { + var options_2 = { // NOT OK, implicitly enabled webPreferences: pref, show: true, frame: true, @@ -34,7 +34,7 @@ function test() { minHeight: 300 }; - var safe_used = { + var safe_used = { // NOT OK, explicitly disabled webPreferences: { nodeIntegration: false, plugins: true, @@ -46,6 +46,7 @@ function test() { var w1 = new BrowserWindow(unsafe_1); var w2 = new BrowserWindow(options_1); var w3 = new BrowserWindow(safe_used); - var w4 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: true}}); - var w5 = new BrowserWindow(options_2); -} \ No newline at end of file + var w4 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: true}}); // NOT OK, `nodeIntegration` enabled + var w5 = new BrowserWindow(options_2); + var w6 = new BrowserWindow(safe_used); +} diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref index 3f8dbad0d57..b0315fd89ad 100644 --- a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref @@ -1 +1 @@ -../../../../src/Electron/EnablingNodeIntegration.ql \ No newline at end of file +Electron/EnablingNodeIntegration.ql From ecd08d4560a05a151044dcf858cfcf101c839911 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Wed, 12 Sep 2018 12:05:57 -0700 Subject: [PATCH 41/73] Chaning EOL in two files --- .../Electron/NodeIntegration/EnablingNodeIntegration.qlref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref index b0315fd89ad..3f8dbad0d57 100644 --- a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref @@ -1 +1 @@ -Electron/EnablingNodeIntegration.ql +../../../../src/Electron/EnablingNodeIntegration.ql \ No newline at end of file From 1220b50737119b08356ecccdf91ed50ac356cd59 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 11 Sep 2018 21:37:02 +0200 Subject: [PATCH 42/73] JS: whitelist _.bindAll-methods in js/unbound-event-handler-receiver --- .../UnboundEventHandlerReceiver.ql | 20 +++++++-- .../UnboundEventHandlerReceiver.expected | 6 +-- .../UnboundEventHandlerReceiver/tst.js | 45 ++++++++++++------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql index 63212b69bf1..8875a376c16 100644 --- a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql +++ b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql @@ -13,16 +13,30 @@ import javascript * Holds if the receiver of `method` is bound in a method of its class. */ private predicate isBoundInMethod(MethodDeclaration method) { - exists (DataFlow::ThisNode thiz, MethodDeclaration bindingMethod | + exists (DataFlow::ThisNode thiz, MethodDeclaration bindingMethod, string name | + name = method.getName() and bindingMethod.getDeclaringClass() = method.getDeclaringClass() and not bindingMethod.isStatic() and - thiz.getBinder().getAstNode() = bindingMethod.getBody() and + thiz.getBinder().getAstNode() = bindingMethod.getBody() | exists (DataFlow::Node rhs, DataFlow::MethodCallNode bind | // this. = .bind(...) - thiz.hasPropertyWrite(method.getName(), rhs) and + thiz.hasPropertyWrite(name, rhs) and bind.flowsTo(rhs) and bind.getMethodName() = "bind" ) + or + exists (DataFlow::MethodCallNode bindAll | + bindAll.getMethodName() = "bindAll" and + thiz.flowsTo(bindAll.getArgument(0)) | + // _.bindAll(this, ) + bindAll.getArgument(1).mayHaveStringValue(name) + or + // _.bindAll(this, [, ]) + exists (DataFlow::ArrayLiteralNode names | + names.flowsTo(bindAll.getArgument(1)) and + names.getAnElement().mayHaveStringValue(name) + ) + ) ) } diff --git a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected index d0cba5116dc..d0c16fa6c0e 100644 --- a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected +++ b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected @@ -1,3 +1,3 @@ -| tst.js:56:18:56:40 | onClick ... bound1} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:14:9:14:12 | this | this | tst.js:13:5:15:5 | unbound ... ;\\n } | unbound1 | -| tst.js:57:18:57:40 | onClick ... bound2} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:18:15:18:18 | this | this | tst.js:17:5:19:5 | unbound ... ;\\n } | unbound2 | -| tst.js:58:18:58:35 | onClick={unbound3} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:22:15:22:18 | this | this | tst.js:21:5:23:5 | unbound ... ;\\n } | unbound3 | +| tst.js:8:18:8:40 | onClick ... bound1} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:35:9:35:12 | this | this | tst.js:34:5:36:5 | unbound ... ;\\n } | unbound1 | +| tst.js:9:18:9:40 | onClick ... bound2} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:39:15:39:18 | this | this | tst.js:38:5:40:5 | unbound ... ;\\n } | unbound2 | +| tst.js:10:18:10:35 | onClick={unbound3} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:43:15:43:18 | this | this | tst.js:42:5:44:5 | unbound ... ;\\n } | unbound3 | diff --git a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js index 196fcea7820..0d9334a9fd8 100644 --- a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js +++ b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js @@ -1,6 +1,25 @@ import React from 'react'; class Component extends React.Component { + + render() { + var unbound3 = this.unbound3; + return
    +
    // NOT OK +
    // NOT OK +
    // NOT OK +
    // OK +
    // OK +
    // OK +
    // OK +
    this.unbound_butInvokedSafely(e)}/> // OK +
    // OK +
    // OK +
    // OK +
    // OK +
    + } + constructor(props) { super(props); this.bound_throughBindInConstructor = this.bound_throughBindInConstructor.bind(this); @@ -8,6 +27,8 @@ class Component extends React.Component { var cmp = this; var bound = (cmp.bound_throughNonSyntacticBindInConstructor.bind(this)); (cmp).bound_throughNonSyntacticBindInConstructor = bound; + _.bindAll(this, 'bound_throughBindAllInConstructor1'); + _.bindAll(this, ['bound_throughBindAllInConstructor2']); } unbound1() { @@ -50,22 +71,6 @@ class Component extends React.Component { this.setState({ }); } - render() { - var unbound3 = this.unbound3; - return
    -
    // NOT OK -
    // NOT OK -
    // NOT OK -
    // OK -
    // OK -
    // OK -
    // OK -
    this.unbound_butInvokedSafely(e)}/> // OK -
    // OK -
    // OK -
    - } - componentWillMount() { this.bound_throughBindInMethod = this.bound_throughBindInMethod.bind(this); } @@ -74,6 +79,14 @@ class Component extends React.Component { this.setState({ }); } + bound_throughBindAllInConstructor1() { + this.setState({ }); + } + + bound_throughBindAllInConstructor2() { + this.setState({ }); + } + } // semmle-extractor-options: --experimental From eb10f603ab16c6e1c319ba91be25d277650d6dd2 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 11 Sep 2018 21:51:51 +0200 Subject: [PATCH 43/73] JS: whitelist decorator-bound methods in js/unbound-event-handler-receiver --- .../src/Expressions/UnboundEventHandlerReceiver.ql | 11 ++++++++++- .../UnboundEventHandlerReceiver.expected | 6 +++--- .../Expressions/UnboundEventHandlerReceiver/tst.js | 12 ++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql index 8875a376c16..1c99ba22caa 100644 --- a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql +++ b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql @@ -10,7 +10,7 @@ import javascript /** - * Holds if the receiver of `method` is bound in a method of its class. + * Holds if the receiver of `method` is bound. */ private predicate isBoundInMethod(MethodDeclaration method) { exists (DataFlow::ThisNode thiz, MethodDeclaration bindingMethod, string name | @@ -38,6 +38,15 @@ private predicate isBoundInMethod(MethodDeclaration method) { ) ) ) + or + exists (Expr decoration, string name | + decoration = method.getADecorator().getExpression() and + name.regexpMatch("(?i).*(bind|bound).*") | + // @autobind + decoration.(Identifier).getName() = name or + // @action.bound + decoration.(PropAccess).getPropertyName() = name + ) } /** diff --git a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected index d0c16fa6c0e..854a77745bf 100644 --- a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected +++ b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected @@ -1,3 +1,3 @@ -| tst.js:8:18:8:40 | onClick ... bound1} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:35:9:35:12 | this | this | tst.js:34:5:36:5 | unbound ... ;\\n } | unbound1 | -| tst.js:9:18:9:40 | onClick ... bound2} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:39:15:39:18 | this | this | tst.js:38:5:40:5 | unbound ... ;\\n } | unbound2 | -| tst.js:10:18:10:35 | onClick={unbound3} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:43:15:43:18 | this | this | tst.js:42:5:44:5 | unbound ... ;\\n } | unbound3 | +| tst.js:8:18:8:40 | onClick ... bound1} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:37:9:37:12 | this | this | tst.js:36:5:38:5 | unbound ... ;\\n } | unbound1 | +| tst.js:9:18:9:40 | onClick ... bound2} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:41:15:41:18 | this | this | tst.js:40:5:42:5 | unbound ... ;\\n } | unbound2 | +| tst.js:10:18:10:35 | onClick={unbound3} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:45:15:45:18 | this | this | tst.js:44:5:46:5 | unbound ... ;\\n } | unbound3 | diff --git a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js index 0d9334a9fd8..838b39c58b4 100644 --- a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js +++ b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js @@ -17,6 +17,8 @@ class Component extends React.Component {
    // OK
    // OK
    // OK +
    // OK +
    // OK
    } @@ -87,6 +89,16 @@ class Component extends React.Component { this.setState({ }); } + @autobind + bound_throughDecorator_autobind() { + this.setState({ }); + } + + @action.bound + bound_throughDecorator_actionBound() { + this.setState({ }); + } + } // semmle-extractor-options: --experimental From fcc33ce93dcf2d13c25351db26e34b3c2a044d5a Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 11 Sep 2018 22:21:02 +0200 Subject: [PATCH 44/73] JS: whitelist auto-bind methods in js/unbound-event-handler-receiver --- .../UnboundEventHandlerReceiver.ql | 39 +++++++++++-------- .../UnboundEventHandlerReceiver.expected | 6 +-- .../UnboundEventHandlerReceiver/tst.js | 21 +++++++++- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql index 1c99ba22caa..de947b250ab 100644 --- a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql +++ b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql @@ -13,28 +13,33 @@ import javascript * Holds if the receiver of `method` is bound. */ private predicate isBoundInMethod(MethodDeclaration method) { - exists (DataFlow::ThisNode thiz, MethodDeclaration bindingMethod, string name | - name = method.getName() and + exists (DataFlow::ThisNode thiz, MethodDeclaration bindingMethod | bindingMethod.getDeclaringClass() = method.getDeclaringClass() and not bindingMethod.isStatic() and thiz.getBinder().getAstNode() = bindingMethod.getBody() | - exists (DataFlow::Node rhs, DataFlow::MethodCallNode bind | - // this. = .bind(...) - thiz.hasPropertyWrite(name, rhs) and - bind.flowsTo(rhs) and - bind.getMethodName() = "bind" - ) + // require("auto-bind")(this) + thiz.flowsTo(DataFlow::moduleImport("auto-bind").getACall().getArgument(0)) or - exists (DataFlow::MethodCallNode bindAll | - bindAll.getMethodName() = "bindAll" and - thiz.flowsTo(bindAll.getArgument(0)) | - // _.bindAll(this, ) - bindAll.getArgument(1).mayHaveStringValue(name) + exists (string name | + name = method.getName() | + exists (DataFlow::Node rhs, DataFlow::MethodCallNode bind | + // this. = .bind(...) + thiz.hasPropertyWrite(name, rhs) and + bind.flowsTo(rhs) and + bind.getMethodName() = "bind" + ) or - // _.bindAll(this, [, ]) - exists (DataFlow::ArrayLiteralNode names | - names.flowsTo(bindAll.getArgument(1)) and - names.getAnElement().mayHaveStringValue(name) + exists (DataFlow::MethodCallNode bindAll | + bindAll.getMethodName() = "bindAll" and + thiz.flowsTo(bindAll.getArgument(0)) | + // _.bindAll(this, ) + bindAll.getArgument(1).mayHaveStringValue(name) + or + // _.bindAll(this, [, ]) + exists (DataFlow::ArrayLiteralNode names | + names.flowsTo(bindAll.getArgument(1)) and + names.getAnElement().mayHaveStringValue(name) + ) ) ) ) diff --git a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected index 854a77745bf..9d3ad4447d7 100644 --- a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected +++ b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/UnboundEventHandlerReceiver.expected @@ -1,3 +1,3 @@ -| tst.js:8:18:8:40 | onClick ... bound1} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:37:9:37:12 | this | this | tst.js:36:5:38:5 | unbound ... ;\\n } | unbound1 | -| tst.js:9:18:9:40 | onClick ... bound2} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:41:15:41:18 | this | this | tst.js:40:5:42:5 | unbound ... ;\\n } | unbound2 | -| tst.js:10:18:10:35 | onClick={unbound3} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:45:15:45:18 | this | this | tst.js:44:5:46:5 | unbound ... ;\\n } | unbound3 | +| tst.js:27:18:27:40 | onClick ... bound1} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:56:9:56:12 | this | this | tst.js:55:5:57:5 | unbound ... ;\\n } | unbound1 | +| tst.js:28:18:28:40 | onClick ... bound2} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:60:15:60:18 | this | this | tst.js:59:5:61:5 | unbound ... ;\\n } | unbound2 | +| tst.js:29:18:29:35 | onClick={unbound3} | The receiver of this event handler call is unbound, `$@` will be `undefined` in the call to $@ | tst.js:64:15:64:18 | this | this | tst.js:63:5:65:5 | unbound ... ;\\n } | unbound3 | diff --git a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js index 838b39c58b4..1b526572225 100644 --- a/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js +++ b/javascript/ql/test/query-tests/Expressions/UnboundEventHandlerReceiver/tst.js @@ -1,6 +1,25 @@ import React from 'react'; +import autoBind from 'auto-bind'; -class Component extends React.Component { +class Component0 extends React.Component { + + render() { + return
    +
    // OK +
    + } + + constructor(props) { + super(props); + autoBind(this); + } + + bound_throughAutoBind() { + this.setState({ }); + } +} + +class Component1 extends React.Component { render() { var unbound3 = this.unbound3; From 52013f3071c3fb678cf24f467f3d1bd3b2229a21 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 13 Sep 2018 08:43:01 +0200 Subject: [PATCH 45/73] JS: change notes for improved js/unbound-event-handler-receiver --- change-notes/1.19/analysis-javascript.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index 5fdf442d128..86c784b1cae 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -13,6 +13,7 @@ | **Query** | **Expected impact** | **Change** | |--------------------------------|----------------------------|----------------------------------------------| | Regular expression injection | Fewer false-positive results | This rule now identifies calls to `String.prototype.search` with more precision. | +| Unbound event handler receiver | Fewer false-positive results | This rule now recognizes additional ways class methods can be bound. | ## Changes to QL libraries From ea37665ec69a93b83995349a49763b7a96955e6d Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 11 Sep 2018 14:27:08 +0200 Subject: [PATCH 46/73] JS: move array-specific taint steps to separate class --- .../javascript/dataflow/TaintTracking.qll | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index 671729b9b2f..03b16417284 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -185,18 +185,44 @@ module TaintTracking { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { succ = this and - ( - exists (Expr e, Expr f | e = this.asExpr() and f = pred.asExpr() | - // arrays with tainted elements and objects with tainted property names are tainted - e.(ArrayExpr).getAnElement() = f or - exists (Property prop | e.(ObjectExpr).getAProperty() = prop | - prop.isComputed() and f = prop.getNameExpr() - ) - or - // awaiting a tainted expression gives a tainted result - e.(AwaitExpr).getOperand() = f + exists (Expr e, Expr f | e = this.asExpr() and f = pred.asExpr() | + // arrays with tainted elements and objects with tainted property names are tainted + e.(ArrayExpr).getAnElement() = f or + exists (Property prop | e.(ObjectExpr).getAProperty() = prop | + prop.isComputed() and f = prop.getNameExpr() ) or + // awaiting a tainted expression gives a tainted result + e.(AwaitExpr).getOperand() = f + ) + or + // reading from a tainted object yields a tainted result + this = succ and + succ.(DataFlow::PropRead).getBase() = pred + or + // iterating over a tainted iterator taints the loop variable + exists (EnhancedForLoop efl, SsaExplicitDefinition ssa | + this = DataFlow::valueNode(efl.getIterationDomain()) and + pred = this and + ssa.getDef() = efl.getIteratorExpr() and + succ = DataFlow::ssaDefinitionNode(ssa) + ) + } + } + + /** + * A taint propagating data flow edge caused by the builtin array functions. + */ + private class ArrayFunctionTaintStep extends AdditionalTaintStep { + + ArrayFunctionTaintStep() { + this = DataFlow::valueNode(_) or + this = DataFlow::parameterNode(_) or + this instanceof DataFlow::PropRead + } + + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + succ = this and ( // `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are // `elt` and `ary`; similar for `forEach` exists (MethodCallExpr m, Function f, int i, SimpleParameter p | @@ -218,19 +244,8 @@ module TaintTracking { // `array.push(e)`: if `e` is tainted, then so is `array` succ.(DataFlow::SourceNode).getAMethodCall("push").getAnArgument() = pred ) - or - // reading from a tainted object yields a tainted result - this = succ and - succ.(DataFlow::PropRead).getBase() = pred - or - // iterating over a tainted iterator taints the loop variable - exists (EnhancedForLoop efl, SsaExplicitDefinition ssa | - this = DataFlow::valueNode(efl.getIterationDomain()) and - pred = this and - ssa.getDef() = efl.getIteratorExpr() and - succ = DataFlow::ssaDefinitionNode(ssa) - ) } + } /** From 763da72ce5275890d0be82fd9adcbe47b2ef53b3 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 11 Sep 2018 15:09:35 +0200 Subject: [PATCH 47/73] JS: modernize old array taint steps --- .../javascript/dataflow/TaintTracking.qll | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index 03b16417284..2ae0a5ae433 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -214,36 +214,33 @@ module TaintTracking { * A taint propagating data flow edge caused by the builtin array functions. */ private class ArrayFunctionTaintStep extends AdditionalTaintStep { + DataFlow::CallNode call; ArrayFunctionTaintStep() { - this = DataFlow::valueNode(_) or - this = DataFlow::parameterNode(_) or - this instanceof DataFlow::PropRead + this = call } override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - succ = this and ( - // `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are - // `elt` and `ary`; similar for `forEach` - exists (MethodCallExpr m, Function f, int i, SimpleParameter p | - (m.getMethodName() = "map" or m.getMethodName() = "forEach") and - (i = 0 or i = 2) and - m.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and - p = f.getParameter(i) and - this = DataFlow::parameterNode(p) and - pred.asExpr() = m.getReceiver() - ) - or - // `array.map` with tainted return value in callback - exists (MethodCallExpr m, Function f | - this.asExpr() = m and - m.getMethodName() = "map" and - m.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow - pred = f.getAReturnedExpr().flow()) - or - // `array.push(e)`: if `e` is tainted, then so is `array` - succ.(DataFlow::SourceNode).getAMethodCall("push").getAnArgument() = pred + // `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are + // `elt` and `ary`; similar for `forEach` + exists (string name, Function f, int i | + (name = "map" or name = "forEach") and + (i = 0 or i = 2) and + call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and + pred.(DataFlow::SourceNode).getAMethodCall(name) = call and + succ = DataFlow::parameterNode(f.getParameter(i)) ) + or + // `array.map` with tainted return value in callback + exists (DataFlow::FunctionNode f | + call.(DataFlow::MethodCallNode).getMethodName() = "map" and + call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow + pred = f.getAReturn() and + succ = call + ) + or + // `array.push(e)`: if `e` is tainted, then so is `array` + succ.(DataFlow::SourceNode).getAMethodCall("push") = call } } From 1a14b13703e9bf6d12b6b989707ed75e98db9296 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 13 Sep 2018 09:53:41 -0700 Subject: [PATCH 48/73] C++: migrate change note --- change-notes/1.18/analysis-cpp.md | 3 +-- change-notes/1.19/analysis-cpp.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 change-notes/1.19/analysis-cpp.md diff --git a/change-notes/1.18/analysis-cpp.md b/change-notes/1.18/analysis-cpp.md index 7f3f2845387..00877a12462 100644 --- a/change-notes/1.18/analysis-cpp.md +++ b/change-notes/1.18/analysis-cpp.md @@ -31,11 +31,10 @@ | [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. | - + ## 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`. -* Added a hash consing library for structural comparison of expressions. diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md new file mode 100644 index 00000000000..17a165e5928 --- /dev/null +++ b/change-notes/1.19/analysis-cpp.md @@ -0,0 +1,20 @@ +# Improvements to C/C++ analysis + +## General improvements + +## New queries + +| **Query** | **Tags** | **Purpose** | +|-----------------------------|-----------|--------------------------------------------------------------------| +| *@name of query (Query ID)* | *Tags* |*Aim of the new query and whether it is enabled by default or not* | + +## Changes to existing queries + +| **Query** | **Expected impact** | **Change** | +|----------------------------|------------------------|------------------------------------------------------------------| +| *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* | + + +## Changes to QL libraries + +* Added a hash consing library for structural comparison of expressions. From 4c13e6b46b182d733bba356479feeab5dbeec8bf Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 11 Sep 2018 15:10:59 +0200 Subject: [PATCH 49/73] JS: add additional array-specific taint steps --- .../javascript/dataflow/TaintTracking.qll | 24 +++++++++++++++-- .../TaintTracking/BasicTaintTracking.expected | 12 +++++++++ .../test/library-tests/TaintTracking/tst.js | 26 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index 2ae0a5ae433..55f8ec8b9e7 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -239,8 +239,28 @@ module TaintTracking { succ = call ) or - // `array.push(e)`: if `e` is tainted, then so is `array` - succ.(DataFlow::SourceNode).getAMethodCall("push") = call + // `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`. + exists (string name | + name = "push" or + name = "unshift" | + pred = call.getAnArgument() and + succ.(DataFlow::SourceNode).getAMethodCall(name) = call + ) + or + // `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`. + exists (string name | + name = "pop" or + name = "shift" or + name = "slice" or + name = "splice" | + call.(DataFlow::MethodCallNode).calls(pred, name) and + succ = call + ) + or + // `e = Array.from(x)`: if `x` is tainted, then so is `e`. + call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and + pred = call.getAnArgument() and + succ = call } } diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index a30f0b1bf2b..6dcfbe315bf 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -3,3 +3,15 @@ | tst.js:2:13:2:20 | source() | tst.js:14:10:14:17 | x.sort() | | tst.js:2:13:2:20 | source() | tst.js:17:10:17:10 | a | | tst.js:2:13:2:20 | source() | tst.js:19:10:19:10 | a | +| tst.js:2:13:2:20 | source() | tst.js:23:10:23:10 | b | +| tst.js:2:13:2:20 | source() | tst.js:25:10:25:16 | x.pop() | +| tst.js:2:13:2:20 | source() | tst.js:26:10:26:18 | x.shift() | +| tst.js:2:13:2:20 | source() | tst.js:27:10:27:18 | x.slice() | +| tst.js:2:13:2:20 | source() | tst.js:28:10:28:19 | x.splice() | +| tst.js:2:13:2:20 | source() | tst.js:30:10:30:22 | Array.from(x) | +| tst.js:2:13:2:20 | source() | tst.js:33:14:33:16 | elt | +| tst.js:2:13:2:20 | source() | tst.js:35:14:35:16 | ary | +| tst.js:2:13:2:20 | source() | tst.js:39:14:39:16 | elt | +| tst.js:2:13:2:20 | source() | tst.js:41:14:41:16 | ary | +| tst.js:2:13:2:20 | source() | tst.js:44:10:44:30 | innocen ... ) => x) | +| tst.js:2:13:2:20 | source() | tst.js:45:10:45:24 | x.map(x2 => x2) | diff --git a/javascript/ql/test/library-tests/TaintTracking/tst.js b/javascript/ql/test/library-tests/TaintTracking/tst.js index 69b5f17074c..48c7ace4611 100644 --- a/javascript/ql/test/library-tests/TaintTracking/tst.js +++ b/javascript/ql/test/library-tests/TaintTracking/tst.js @@ -18,4 +18,30 @@ function test() { a.push(x); sink(a); // NOT OK + var b = []; + b.unshift(x); + sink(b); // NOT OK + + sink(x.pop()); // NOT OK + sink(x.shift()); // NOT OK + sink(x.slice()); // NOT OK + sink(x.splice()); // NOT OK + + sink(Array.from(x)); // NOT OK + + x.map((elt, i, ary) => { + sink(elt); // NOT OK + sink(i); // OK + sink(ary); // NOT OK + }); + + x.forEach((elt, i, ary) => { + sink(elt); // NOT OK + sink(i); // OK + sink(ary); // NOT OK + }); + + sink(innocent.map(() => x)); // NOT OK + sink(x.map(x2 => x2)); // NOT OK + } From cb2bd9e0aef050188b38e596a5af5da94fe187b9 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 13 Sep 2018 15:52:40 +0200 Subject: [PATCH 50/73] JS: change notes for additional array taint steps --- change-notes/1.19/analysis-javascript.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index 5fdf442d128..0e9b224e26d 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -2,6 +2,8 @@ ## General improvements +* Modelling of taint flow through array operations has been improved. This may give additional results for the security queries. + ## New queries | **Query** | **Tags** | **Purpose** | From 28050e1415de70c25b050237ab7e9900f81b0824 Mon Sep 17 00:00:00 2001 From: Raul Garcia Date: Thu, 13 Sep 2018 15:44:32 -0700 Subject: [PATCH 51/73] Change to cpp/overflow-buffer to detect access to an array using a negative index (static, out of range access, lower bound). --- .gitignore | 4 ++++ cpp/ql/src/Security/CWE/CWE-119/OverflowBuffer.ql | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 31f8ccd9abf..4b055e55a09 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ # qltest projects and artifacts */ql/test/**/*.testproj */ql/test/**/*.actual +/.vs/slnx.sqlite +/.vs/ql/v15/Browse.VC.opendb +/.vs/ql/v15/Browse.VC.db +/.vs/ProjectSettings.json diff --git a/cpp/ql/src/Security/CWE/CWE-119/OverflowBuffer.ql b/cpp/ql/src/Security/CWE/CWE-119/OverflowBuffer.ql index c15552384e9..ec1d84b8d29 100644 --- a/cpp/ql/src/Security/CWE/CWE-119/OverflowBuffer.ql +++ b/cpp/ql/src/Security/CWE/CWE-119/OverflowBuffer.ql @@ -29,7 +29,7 @@ from BufferAccess ba, string bufferDesc, int accessSize, int accessType, where accessSize = ba.getSize() and bufferSize = getBufferSize(ba.getBuffer(bufferDesc, accessType), bufferAlloc) - and accessSize > bufferSize + and (accessSize > bufferSize or (accessSize <= 0 and accessType = 3)) and if accessType = 1 then ( message = "This '" + ba.getName() + "' operation accesses " + plural(accessSize, " byte", " bytes") @@ -41,8 +41,13 @@ where accessSize = ba.getSize() + " but the $@ is only " + plural(bufferSize, " byte", " bytes") + "." ) else ( - message = "This array indexing operation accesses byte offset " - + (accessSize - 1) + " but the $@ is only " - + plural(bufferSize, " byte", " bytes") + "." + if accessSize > 0 then ( + message = "This array indexing operation accesses byte offset " + + (accessSize - 1) + " but the $@ is only " + + plural(bufferSize, " byte", " bytes") + "." + ) else ( + message = "This array indexing operation accesses a negative index " + + ((accessSize/ba.getActualType().getSize()) - 1) + " on the $@." + ) ) select ba, message, bufferAlloc, bufferDesc From 81aeda69e1270dbb48ee13c637b46c6670f6f73a Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 13 Sep 2018 21:46:46 +0200 Subject: [PATCH 52/73] JS: lower @precision of js/remote-property-injection --- change-notes/1.19/analysis-javascript.md | 2 +- javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index 86c784b1cae..b94b015d372 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -14,6 +14,6 @@ |--------------------------------|----------------------------|----------------------------------------------| | Regular expression injection | Fewer false-positive results | This rule now identifies calls to `String.prototype.search` with more precision. | | Unbound event handler receiver | Fewer false-positive results | This rule now recognizes additional ways class methods can be bound. | - +| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. | ## Changes to QL libraries diff --git a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql index e3a9dcd74ba..ae70f83ed87 100644 --- a/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql +++ b/javascript/ql/src/Security/CWE-400/RemotePropertyInjection.ql @@ -5,7 +5,7 @@ * * @kind problem * @problem.severity warning - * @precision high + * @precision medium * @id js/remote-property-injection * @tags security * external/cwe/cwe-250 From 8de269e1fb9abbab7729406718104e432b3a444e Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 30 Aug 2018 08:51:57 +0200 Subject: [PATCH 53/73] JS: add support for `fs-extra` in `NodeJSFileSystemAccess` --- javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index ba4dc9731e7..1d911f7a2e2 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -338,14 +338,14 @@ module NodeJSLib { /** - * A call to a method from module `fs` or `graceful-fs`. + * A call to a method from module `fs`, `graceful-fs` or `fs-extra`. */ private class NodeJSFileSystemAccess extends FileSystemAccess, DataFlow::CallNode { string methodName; NodeJSFileSystemAccess() { exists (string moduleName | this = DataFlow::moduleMember(moduleName, methodName).getACall() | - moduleName = "fs" or moduleName = "graceful-fs" + moduleName = "fs" or moduleName = "graceful-fs" or moduleName = "fs-extra" ) } From 6d3c1a1d225a9cb01e1a9af27ad3400bd7a1e9dd Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 30 Aug 2018 08:59:22 +0200 Subject: [PATCH 54/73] JS: introduce `fsModuleMember` --- .../semmle/javascript/frameworks/NodeJSLib.qll | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 1d911f7a2e2..b277663b3dd 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -336,6 +336,17 @@ module NodeJSLib { ) } + /** + * A member `member` from module `fs` or its drop-in replacements `graceful-fs` or `fs-extra`. + */ + private DataFlow::SourceNode fsModuleMember(string member) { + exists (string moduleName | + moduleName = "fs" or + moduleName = "graceful-fs" or + moduleName = "fs-extra" | + result = DataFlow::moduleMember(moduleName, member) + ) + } /** * A call to a method from module `fs`, `graceful-fs` or `fs-extra`. @@ -344,9 +355,7 @@ module NodeJSLib { string methodName; NodeJSFileSystemAccess() { - exists (string moduleName | this = DataFlow::moduleMember(moduleName, methodName).getACall() | - moduleName = "fs" or moduleName = "graceful-fs" or moduleName = "fs-extra" - ) + this = fsModuleMember(methodName).getACall() } override DataFlow::Node getAPathArgument() { From e2fac8a03cae6cd04eae640ce8419e8f5dc08124 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 30 Aug 2018 12:52:39 +0200 Subject: [PATCH 55/73] JS: introduce concept: `FileNameSource` --- javascript/ql/src/semmle/javascript/Concepts.qll | 7 +++++++ .../semmle/javascript/frameworks/NodeJSLib.qll | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/Concepts.qll b/javascript/ql/src/semmle/javascript/Concepts.qll index 1ab9f8cba2e..ce470f882b7 100644 --- a/javascript/ql/src/semmle/javascript/Concepts.qll +++ b/javascript/ql/src/semmle/javascript/Concepts.qll @@ -29,6 +29,13 @@ abstract class FileSystemAccess extends DataFlow::Node { abstract DataFlow::Node getAPathArgument(); } +/** + * A data flow node that contains a file name or an array of file names from the local file system. + */ +abstract class FileNameSource extends DataFlow::Node { + +} + /** * A data flow node that performs a database access. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index b277663b3dd..4328d8fdee4 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -365,6 +365,22 @@ module NodeJSLib { } } + /** + * A data flow node that contains a file name or an array of file names from the local file system. + */ + private class NodeJSFileNameSource extends FileNameSource { + + NodeJSFileNameSource() { + exists (string name | + name = "readdir" or + name = "realpath" | + this = fsModuleMember(name).getACall().getCallback([1..2]).getParameter(1) or + this = fsModuleMember(name + "Sync").getACall() + ) + } + + } + /** * A call to a method from module `child_process`. */ From 33f98dd1a7c6381a915faa248550d50c024731ee Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Thu, 30 Aug 2018 12:52:59 +0200 Subject: [PATCH 56/73] JS: add query: js/stored-xss --- javascript/config/suites/javascript/security | 1 + .../ql/src/Security/CWE-079/StoredXss.qhelp | 63 +++++++++++++++++++ .../ql/src/Security/CWE-079/StoredXss.ql | 20 ++++++ .../Security/CWE-079/examples/StoredXss.js | 13 ++++ .../CWE-079/examples/StoredXssGood.js | 14 +++++ .../security/dataflow/StoredXss.qll | 62 ++++++++++++++++++ .../Security/CWE-079/StoredXss.expected | 4 ++ .../Security/CWE-079/StoredXss.qlref | 1 + .../Security/CWE-079/xss-through-filenames.js | 40 ++++++++++++ 9 files changed, 218 insertions(+) create mode 100644 javascript/ql/src/Security/CWE-079/StoredXss.qhelp create mode 100644 javascript/ql/src/Security/CWE-079/StoredXss.ql create mode 100644 javascript/ql/src/Security/CWE-079/examples/StoredXss.js create mode 100644 javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js create mode 100644 javascript/ql/src/semmle/javascript/security/dataflow/StoredXss.qll create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/StoredXss.expected create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/StoredXss.qlref create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/xss-through-filenames.js diff --git a/javascript/config/suites/javascript/security b/javascript/config/suites/javascript/security index 26debb63a24..e55f89019f6 100644 --- a/javascript/config/suites/javascript/security +++ b/javascript/config/suites/javascript/security @@ -2,6 +2,7 @@ + semmlecode-javascript-queries/Security/CWE-022/TaintedPath.ql: /Security/CWE/CWE-022 + semmlecode-javascript-queries/Security/CWE-078/CommandInjection.ql: /Security/CWE/CWE-078 + semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079 ++ semmlecode-javascript-queries/Security/CWE-079/StoredXss.ql: /Security/CWE/CWE-079 + semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079 + semmlecode-javascript-queries/Security/CWE-089/SqlInjection.ql: /Security/CWE/CWE-089 + semmlecode-javascript-queries/Security/CWE-094/CodeInjection.ql: /Security/CWE/CWE-094 diff --git a/javascript/ql/src/Security/CWE-079/StoredXss.qhelp b/javascript/ql/src/Security/CWE-079/StoredXss.qhelp new file mode 100644 index 00000000000..1c3fde01798 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/StoredXss.qhelp @@ -0,0 +1,63 @@ + + + + +

    + + Directly using uncontrolled stored value (for example, file names) to + create HTML content without properly sanitizing the input first, + allows for a cross-site scripting vulnerability. + +

    +

    + + This kind of vulnerability is also called stored cross-site + scripting, to distinguish it from other types of cross-site scripting. + +

    +
    + + +

    + + To guard against cross-site scripting, consider using contextual + output encoding/escaping before using uncontrolled stored values to + create HTML content, or one of the other solutions that are mentioned + in the references. + +

    +
    + + +

    + + The following example code writes file names directly to a HTTP + response. This leaves the website vulnerable to cross-site scripting, + if an attacker can choose the file names on the disk. + +

    + +

    + Sanitizing the file names prevents the vulnerability: +

    + +
    + + +
  • + OWASP: + XSS + (Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • + OWASP + Types of Cross-Site + Scripting. +
  • +
  • + Wikipedia: Cross-site scripting. +
  • +
    +
    diff --git a/javascript/ql/src/Security/CWE-079/StoredXss.ql b/javascript/ql/src/Security/CWE-079/StoredXss.ql new file mode 100644 index 00000000000..429bccdf660 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/StoredXss.ql @@ -0,0 +1,20 @@ +/** + * @name Stored cross-site scripting + * @description Using uncontrolled stored values in HTML allows for + * a stored cross-site scripting vulnerability. + * @kind problem + * @problem.severity error + * @precision high + * @id js/stored-xss + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import javascript +import semmle.javascript.security.dataflow.StoredXss::StoredXss + +from Configuration xss, DataFlow::Node source, DataFlow::Node sink +where xss.hasFlow(source, sink) +select sink, "Stored cross-site scripting vulnerability due to $@.", + source, "stored value" \ No newline at end of file diff --git a/javascript/ql/src/Security/CWE-079/examples/StoredXss.js b/javascript/ql/src/Security/CWE-079/examples/StoredXss.js new file mode 100644 index 00000000000..2ae6da6b4a2 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/examples/StoredXss.js @@ -0,0 +1,13 @@ +var express = require('express'), + fs = require('fs'); + +express().get('/list-directory', function(req, res) { + fs.readdir('/public', function (error, fileNames) { + var list = '
      '; + fileNames.forEach(fileName => { + list += '
    • ' + fileName '
    • '; // BAD: `fileName` can contain HTML elements + }); + list += '
    ' + res.send(list); + }); +}); diff --git a/javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js b/javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js new file mode 100644 index 00000000000..61d18818e8e --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js @@ -0,0 +1,14 @@ +var express = require('express'), + fs = require('fs'), + escape = require('escape-html'); + +express().get('/list-directory', function(req, res) { + fs.readdir('/public', function (error, fileNames) { + var list = '
      '; + fileNames.forEach(fileName => { + list += '
    • ' + escape(fileName) '
    • '; // GOOD: escaped `fileName` can not contain HTML elements + }); + list += '
    ' + res.send(list); + }); +}); diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/StoredXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/StoredXss.qll new file mode 100644 index 00000000000..8606d24af53 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/dataflow/StoredXss.qll @@ -0,0 +1,62 @@ +/** + * Provides a taint-tracking configuration for reasoning about stored + * cross-site scripting vulnerabilities. + */ + +import javascript +import semmle.javascript.security.dataflow.RemoteFlowSources +import semmle.javascript.security.dataflow.ReflectedXss as ReflectedXss +import semmle.javascript.security.dataflow.DomBasedXss as DomBasedXss + +module StoredXss { + /** + * A data flow source for XSS vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for XSS vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for XSS vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A taint-tracking configuration for reasoning about XSS. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "StoredXss" } + + 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 + } + } + + /** A file name, considered as a flow source for stored XSS. */ + class FileNameSourceAsSource extends Source { + FileNameSourceAsSource() { + this instanceof FileNameSource + } + } + + /** An ordinary XSS sink, considered as a flow sink for stored XSS. */ + class XssSinkAsSink extends Sink { + XssSinkAsSink() { + this instanceof ReflectedXss::ReflectedXss::Sink or + this instanceof DomBasedXss::DomBasedXss::Sink + } + } + +} diff --git a/javascript/ql/test/query-tests/Security/CWE-079/StoredXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/StoredXss.expected new file mode 100644 index 00000000000..9d9a94ff27e --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/StoredXss.expected @@ -0,0 +1,4 @@ +| xss-through-filenames.js:8:18:8:23 | files1 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:7:43:7:48 | files1 | stored value | +| xss-through-filenames.js:26:19:26:24 | files1 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:25:43:25:48 | files1 | stored value | +| xss-through-filenames.js:33:19:33:24 | files2 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:25:43:25:48 | files1 | stored value | +| xss-through-filenames.js:37:19:37:24 | files3 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:25:43:25:48 | files1 | stored value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/StoredXss.qlref b/javascript/ql/test/query-tests/Security/CWE-079/StoredXss.qlref new file mode 100644 index 00000000000..27140feea76 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/StoredXss.qlref @@ -0,0 +1 @@ +Security/CWE-079/StoredXss.ql \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-079/xss-through-filenames.js b/javascript/ql/test/query-tests/Security/CWE-079/xss-through-filenames.js new file mode 100644 index 00000000000..c04e0d784ef --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/xss-through-filenames.js @@ -0,0 +1,40 @@ +var http = require('http'); +var fs = require('fs'); + +var express = require('express'); + +express().get('/', function(req, res) { + fs.readdir("/myDir", function (error, files1) { + res.send(files1); // NOT OK + }); +}); + +/** + * The essence of a real world vulnerability. + */ +http.createServer(function (req, res) { + + function format(files2) { + var files3 = []; + files2.sort(sort).forEach(function (file) { + files3.push('
  • ' + file + '
  • '); + }); + return files3.join(''); + } + + fs.readdir("/myDir", function (error, files1) { + res.write(files1); // NOT OK + + var dirs = []; + var files2 = []; + files1.forEach(function (file) { + files2.push(file); + }); + res.write(files2); // NOT OK + + var files3 = format(files2); + + res.write(files3); // NOT OK + + }); +}); From 5781b518bcdd65ba6e788b225f795f001990c25f Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 3 Sep 2018 15:53:42 +0200 Subject: [PATCH 57/73] JS: change notes for js/stored-xss --- change-notes/1.19/analysis-javascript.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index 13a8680c27b..888e1e344c1 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -4,11 +4,15 @@ * Modelling of taint flow through array operations has been improved. This may give additional results for the security queries. +* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following features: + - file system access, for example through [fs-extra](https://github.com/jprichardson/node-fs-extra) or [globby](https://www.npmjs.com/package/globby) + + ## New queries -| **Query** | **Tags** | **Purpose** | -|-----------------------------|-----------|--------------------------------------------------------------------| -| *@name of query (Query ID)* | *Tags* |*Aim of the new query and whether it is enabled by default or not* | +| **Query** | **Tags** | **Purpose** | +|-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on lgtm by default. | ## Changes to existing queries From 444a09a17c40f0e871f597a803ddf3e12cdef64a Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Wed, 5 Sep 2018 15:38:25 +0200 Subject: [PATCH 58/73] JS: add models of five file system libraries --- javascript/ql/src/javascript.qll | 1 + .../semmle/javascript/frameworks/Files.qll | 103 ++++++++++++++++++ .../Concepts/FileNameSource.expected | 12 ++ .../frameworks/Concepts/FileNameSource.ql | 3 + .../frameworks/Concepts/tst-file-names.js | 29 +++++ 5 files changed, 148 insertions(+) create mode 100644 javascript/ql/src/semmle/javascript/frameworks/Files.qll create mode 100644 javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.expected create mode 100644 javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.ql create mode 100644 javascript/ql/test/library-tests/frameworks/Concepts/tst-file-names.js diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index 16bdff91f76..a5a78a9ea5f 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -59,6 +59,7 @@ import semmle.javascript.frameworks.Credentials import semmle.javascript.frameworks.CryptoLibraries import semmle.javascript.frameworks.DigitalOcean import semmle.javascript.frameworks.Electron +import semmle.javascript.frameworks.Files import semmle.javascript.frameworks.jQuery import semmle.javascript.frameworks.LodashUnderscore import semmle.javascript.frameworks.Logging diff --git a/javascript/ql/src/semmle/javascript/frameworks/Files.qll b/javascript/ql/src/semmle/javascript/frameworks/Files.qll new file mode 100644 index 00000000000..0138e8531fb --- /dev/null +++ b/javascript/ql/src/semmle/javascript/frameworks/Files.qll @@ -0,0 +1,103 @@ +/** + * Provides classes for working with file system libraries. + */ + +import javascript + +/** + * A file name from the `walk-sync` library. + */ +private class WalkSyncFileNameSource extends FileNameSource { + + WalkSyncFileNameSource() { + // `require('walkSync')()` + this = DataFlow::moduleImport("walkSync").getACall() + } + +} + +/** + * A file name or an array of file names from the `walk` library. + */ +private class WalkFileNameSource extends FileNameSource { + + WalkFileNameSource() { + // `stats.name` in `require('walk').walk(_).on(_, (_, stats) => stats.name)` + exists (DataFlow::FunctionNode callback | + callback = DataFlow::moduleMember("walk", "walk").getACall().getAMethodCall("on").getCallback(1) | + this = callback.getParameter(1).getAPropertyRead("name") + ) + } + +} + +/** + * A file name or an array of file names from the `glob` library. + */ +private class GlobFileNameSource extends FileNameSource { + + GlobFileNameSource() { + exists (string moduleName | + moduleName = "glob" | + // `require('glob').sync(_)` + this = DataFlow::moduleMember(moduleName, "sync").getACall() + or + // `name` in `require('glob')(_, (e, name) => ...)` + this = DataFlow::moduleImport(moduleName).getACall().getCallback([1..2]).getParameter(1) + or + exists (DataFlow::NewNode instance | + instance = DataFlow::moduleMember(moduleName, "Glob").getAnInstantiation() | + // `name` in `new require('glob').Glob(_, (e, name) => ...)` + this = instance.getCallback([1..2]).getParameter(1) or + // `new require('glob').Glob(_).found` + this = instance.getAPropertyRead("found") + ) + ) + } + +} + +/** + * A file name or an array of file names from the `globby` library. + */ +private class GlobbyFileNameSource extends FileNameSource { + + GlobbyFileNameSource() { + exists (string moduleName | + moduleName = "globby" | + // `require('globby').sync(_)` + this = DataFlow::moduleMember(moduleName, "sync").getACall() + or + // `files` in `require('globby')(_).then(files => ...)` + this = DataFlow::moduleImport(moduleName).getACall().getAMethodCall("then").getCallback(0).getParameter(0) + ) + } + +} + +/** + * A file name or an array of file names from the `fast-glob` library. + */ +private class FastGlobFileNameSource extends FileNameSource { + + FastGlobFileNameSource() { + exists (string moduleName | + moduleName = "fast-glob" | + // `require('fast-glob').sync(_)` + this = DataFlow::moduleMember(moduleName, "sync").getACall() + or + exists (DataFlow::SourceNode f | + f = DataFlow::moduleImport(moduleName) + or + f = DataFlow::moduleMember(moduleName, "async") | + // `files` in `require('fast-glob')(_).then(files => ...)` and + // `files` in `require('fast-glob').async(_).then(files => ...)` + this = f.getACall().getAMethodCall("then").getCallback(0).getParameter(0) + ) + or + // `file` in `require('fast-glob').stream(_).on(_, file => ...)` + this = DataFlow::moduleMember(moduleName, "stream").getACall().getAMethodCall("on").getCallback(1).getParameter(0) + ) + } + +} diff --git a/javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.expected b/javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.expected new file mode 100644 index 00000000000..bf05cf4163e --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.expected @@ -0,0 +1,12 @@ +| tst-file-names.js:7:1:7:10 | walkSync() | +| tst-file-names.js:9:35:9:44 | stats.name | +| tst-file-names.js:11:1:11:12 | glob.sync(_) | +| tst-file-names.js:13:13:13:16 | name | +| tst-file-names.js:15:22:15:25 | name | +| tst-file-names.js:17:1:17:22 | new glo ... ).found | +| tst-file-names.js:19:1:19:14 | globby.sync(_) | +| tst-file-names.js:21:16:21:20 | files | +| tst-file-names.js:23:1:23:16 | fastGlob.sync(_) | +| tst-file-names.js:25:18:25:22 | files | +| tst-file-names.js:27:24:27:28 | files | +| tst-file-names.js:29:27:29:30 | file | diff --git a/javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.ql b/javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.ql new file mode 100644 index 00000000000..952897d5820 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.ql @@ -0,0 +1,3 @@ +import javascript + +select any(FileNameSource s) \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/Concepts/tst-file-names.js b/javascript/ql/test/library-tests/frameworks/Concepts/tst-file-names.js new file mode 100644 index 00000000000..293f2aa86d1 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Concepts/tst-file-names.js @@ -0,0 +1,29 @@ +let walkSync = require('walkSync'), + walk = require('walk'), + glob = require('glob'), + globby = require('globby'), + fastGlob = require('fast-glob'); + +walkSync(); + +walk.walk(_).on(_, (_, stats) => stats.name); // XXX + +glob.sync(_); + +glob(_, (e, name) => name); + +new glob.Glob(_, (e, name) => name); + +new glob.Glob(_).found; + +globby.sync(_); + +globby(_).then(files => files) + +fastGlob.sync(_); + +fastGlob(_).then(files => files); + +fastGlob.async(_).then(files => files); + +fastGlob.stream(_).on(_, file => file); // XXX From 7071c75567631e54ff33fa70f892cfab1e441050 Mon Sep 17 00:00:00 2001 From: Behrang Fouladi Azarnaminy Date: Fri, 14 Sep 2018 09:03:48 -0700 Subject: [PATCH 59/73] revert "Chaning EOL in two files" This reverts commit ecd08d4560a05a151044dcf858cfcf101c839911. --- .../Electron/NodeIntegration/EnablingNodeIntegration.qlref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref index 3f8dbad0d57..b0315fd89ad 100644 --- a/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref +++ b/javascript/ql/test/query-tests/Electron/NodeIntegration/EnablingNodeIntegration.qlref @@ -1 +1 @@ -../../../../src/Electron/EnablingNodeIntegration.ql \ No newline at end of file +Electron/EnablingNodeIntegration.ql From bb48421d77788b8867ba90638dd4d4b4aeb7bb28 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 17 Sep 2018 11:08:35 +0200 Subject: [PATCH 60/73] JS: address doc review comments --- change-notes/1.19/analysis-javascript.md | 2 +- javascript/ql/src/Security/CWE-079/examples/StoredXss.js | 3 ++- javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index 888e1e344c1..e257a365883 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -12,7 +12,7 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on lgtm by default. | +| Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on LGTM by default. | ## Changes to existing queries diff --git a/javascript/ql/src/Security/CWE-079/examples/StoredXss.js b/javascript/ql/src/Security/CWE-079/examples/StoredXss.js index 2ae6da6b4a2..226cdced23d 100644 --- a/javascript/ql/src/Security/CWE-079/examples/StoredXss.js +++ b/javascript/ql/src/Security/CWE-079/examples/StoredXss.js @@ -5,7 +5,8 @@ express().get('/list-directory', function(req, res) { fs.readdir('/public', function (error, fileNames) { var list = '
      '; fileNames.forEach(fileName => { - list += '
    • ' + fileName '
    • '; // BAD: `fileName` can contain HTML elements + // BAD: `fileName` can contain HTML elements + list += '
    • ' + fileName '
    • '; }); list += '
    ' res.send(list); diff --git a/javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js b/javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js index 61d18818e8e..0a05c3a7d45 100644 --- a/javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js +++ b/javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js @@ -6,7 +6,8 @@ express().get('/list-directory', function(req, res) { fs.readdir('/public', function (error, fileNames) { var list = '
      '; fileNames.forEach(fileName => { - list += '
    • ' + escape(fileName) '
    • '; // GOOD: escaped `fileName` can not contain HTML elements + // GOOD: escaped `fileName` can not contain HTML elements + list += '
    • ' + escape(fileName) '
    • '; }); list += '
    ' res.send(list); From e2cdf5d7ed7dd8b3b2d519c42c518d3a0fec733c Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 9 Aug 2018 14:53:31 +0100 Subject: [PATCH 61/73] JavaScript: add string concatenation library --- javascript/ql/src/javascript.qll | 1 + .../semmle/javascript/StringConcatenation.qll | 71 ++++++++++++++++ .../src/semmle/javascript/dataflow/Nodes.qll | 41 ++++++++++ .../javascript/dataflow/TaintTracking.qll | 15 +--- .../security/dataflow/DomBasedXss.qll | 15 ++-- .../dataflow/ServerSideUrlRedirect.qll | 18 +--- .../security/dataflow/UrlConcatenation.qll | 27 ++---- .../StringConcatenation/ContainsTwo.expected | 28 +++++++ .../StringConcatenation/ContainsTwo.ql | 15 ++++ .../library-tests/StringConcatenation/tst.js | 82 +++++++++++++++++++ 10 files changed, 259 insertions(+), 54 deletions(-) create mode 100644 javascript/ql/src/semmle/javascript/StringConcatenation.qll create mode 100644 javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected create mode 100644 javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.ql create mode 100644 javascript/ql/test/library-tests/StringConcatenation/tst.js diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index 16bdff91f76..cdff23b3fe1 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -38,6 +38,7 @@ import semmle.javascript.Regexp import semmle.javascript.SSA import semmle.javascript.StandardLibrary import semmle.javascript.Stmt +import semmle.javascript.StringConcatenation import semmle.javascript.Templates import semmle.javascript.Tokens import semmle.javascript.TypeScript diff --git a/javascript/ql/src/semmle/javascript/StringConcatenation.qll b/javascript/ql/src/semmle/javascript/StringConcatenation.qll new file mode 100644 index 00000000000..bdbdd787133 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/StringConcatenation.qll @@ -0,0 +1,71 @@ +/** + * Provides predicates for analyzing string concatenations and their operands. + */ +import javascript + +module StringConcatenation { + /** Gets a data flow node referring to the result of the given concatenation. */ + private DataFlow::Node getAssignAddResult(AssignAddExpr expr) { + result = expr.flow() + or + exists (SsaExplicitDefinition def | def.getDef() = expr | + result = DataFlow::valueNode(def.getVariable().getAUse())) + } + + /** Gets the `n`th operand to the string concatenation defining `node`. */ + DataFlow::Node getOperand(DataFlow::Node node, int n) { + exists (AddExpr add | node = add.flow() | + n = 0 and result = add.getLeftOperand().flow() + or + n = 1 and result = add.getRightOperand().flow()) + or + exists (TemplateLiteral template | node = template.flow() | + result = template.getElement(n).flow() and + not exists (TaggedTemplateExpr tag | template = tag.getTemplate())) + or + exists (AssignAddExpr assign | node = getAssignAddResult(assign) | + n = 0 and result = assign.getLhs().flow() + or + n = 1 and result = assign.getRhs().flow()) + or + exists (DataFlow::ArrayCreationNode array | + node = array.getAMethodCall("join") and + node.(DataFlow::MethodCallNode).getArgument(0).mayHaveStringValue("") and + result = array.getElement(n)) + } + + /** Gets an operand to the string concatenation defining `node`. */ + DataFlow::Node getAnOperand(DataFlow::Node node) { + result = getOperand(node, _) + } + + /** Gets the number of operands to the given concatenation. */ + int getNumOperand(DataFlow::Node node) { + result = strictcount(getAnOperand(node)) + } + + /** Gets the first operand to the string concatenation defining `node`. */ + DataFlow::Node getFirstOperand(DataFlow::Node node) { + result = getOperand(node, 0) + } + + /** Gets the last operand to the string concatenation defining `node`. */ + DataFlow::Node getLastOperand(DataFlow::Node node) { + result = getOperand(node, getNumOperand(node) - 1) + } + + /** + * Holds if `src` flows to `dst` through the `n`th operand of the given concatenation operator. + */ + predicate taintStep(DataFlow::Node src, DataFlow::Node dst, DataFlow::Node operator, int n) { + src = getOperand(dst, n) and + operator = dst + } + + /** + * Holds if there is a taint step from `src` to `dst` through string concatenation. + */ + predicate taintStep(DataFlow::Node src, DataFlow::Node dst) { + taintStep(src, dst, _, _) + } +} diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 78d819d490b..facd7aca137 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -304,6 +304,47 @@ class ArrayLiteralNode extends DataFlow::ValueNode, DataFlow::DefaultSourceNode } +/** A data flow node corresponding to a `new Array()` or `Array()` invocation. */ +class ArrayConstructorInvokeNode extends DataFlow::InvokeNode { + ArrayConstructorInvokeNode() { + getCallee() = DataFlow::globalVarRef("Array") + } + + /** Gets the `i`th initial element of this array, if one is provided. */ + DataFlow::ValueNode getElement(int i) { + getNumArgument() > 1 and // A single-argument invocation specifies the array length, not an element. + result = getArgument(i) + } + + /** Gets an initial element of this array, if one is provided. */ + DataFlow::ValueNode getAnElement() { + getNumArgument() > 1 and + result = getAnArgument() + } +} + +/** + * A data flow node corresponding to the creation or a new array, either through an array literal + * or an invocation of the `Array` constructor. + */ +class ArrayCreationNode extends DataFlow::ValueNode, DataFlow::DefaultSourceNode { + ArrayCreationNode() { + this instanceof ArrayLiteralNode or + this instanceof ArrayConstructorInvokeNode + } + + /** Gets the `i`th initial element of this array, if one is provided. */ + DataFlow::ValueNode getElement(int i) { + result = this.(ArrayLiteralNode).getElement(i) or + result = this.(ArrayConstructorInvokeNode).getElement(i) + } + + /** Gets an initial element of this array, if one if provided. */ + DataFlow::ValueNode getAnElement() { + result = getElement(_) + } +} + /** * A data flow node corresponding to a `default` import from a module, or a * (AMD or CommonJS) `require` of a module. diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index 55f8ec8b9e7..1e6e47d254d 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -358,19 +358,8 @@ module TaintTracking { */ class StringConcatenationTaintStep extends AdditionalTaintStep, DataFlow::ValueNode { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - succ = this and - ( - // addition propagates taint - astNode.(AddExpr).getAnOperand() = pred.asExpr() or - astNode.(AssignAddExpr).getAChildExpr() = pred.asExpr() or - exists (SsaExplicitDefinition ssa | - astNode = ssa.getVariable().getAUse() and - pred.asExpr().(AssignAddExpr) = ssa.getDef() - ) - or - // templating propagates taint - astNode.(TemplateLiteral).getAnElement() = pred.asExpr() - ) + succ = this and + StringConcatenation::taintStep(pred, succ) } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll index dc248e2282e..5df5c1436e6 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll @@ -77,9 +77,9 @@ module DomBasedXss { or // or it doesn't start with something other than `<`, and so at least // _may_ be interpreted as HTML - not exists (Expr prefix, string strval | + not exists (DataFlow::Node prefix, string strval | isPrefixOfJQueryHtmlString(astNode, prefix) and - strval = prefix.getStringValue() and + strval = prefix.asExpr().getStringValue() and not strval.regexpMatch("\\s*<.*") ) ) @@ -93,13 +93,14 @@ module DomBasedXss { * Holds if `prefix` is a prefix of `htmlString`, which may be intepreted as * HTML by a jQuery method. */ - private predicate isPrefixOfJQueryHtmlString(Expr htmlString, Expr prefix) { + private predicate isPrefixOfJQueryHtmlString(Expr htmlString, DataFlow::Node prefix) { any(JQueryMethodCall call).interpretsArgumentAsHtml(htmlString) and - prefix = htmlString + prefix = htmlString.flow() or - exists (Expr pred | isPrefixOfJQueryHtmlString(htmlString, pred) | - prefix = pred.(AddExpr).getLeftOperand() or - prefix = pred.(ParExpr).getExpression() + exists (DataFlow::Node pred | isPrefixOfJQueryHtmlString(htmlString, pred) | + prefix = StringConcatenation::getFirstOperand(pred) + or + prefix = pred.getAPredecessor() ) } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ServerSideUrlRedirect.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ServerSideUrlRedirect.qll index e45d8ddadbc..7836d2c594e 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ServerSideUrlRedirect.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ServerSideUrlRedirect.qll @@ -34,33 +34,23 @@ module ServerSideUrlRedirect { ) } } - - /** - * Gets the left operand of `nd` if it is a concatenation. - */ - private DataFlow::Node getPrefixOperand(DataFlow::Node nd) { - exists (Expr e | e instanceof AddExpr or e instanceof AssignAddExpr | - nd = DataFlow::valueNode(e) and - result = DataFlow::valueNode(e.getChildExpr(0)) - ) - } /** * Gets a node that is transitively reachable from `nd` along prefix predecessor edges. */ private DataFlow::Node prefixCandidate(Sink sink) { result = sink or - result = getPrefixOperand(prefixCandidate(sink)) or - result = prefixCandidate(sink).getAPredecessor() + result = prefixCandidate(sink).getAPredecessor() or + result = StringConcatenation::getFirstOperand(prefixCandidate(sink)) } - + /** * Gets an expression that may end up being a prefix of the string concatenation `nd`. */ private Expr getAPrefix(Sink sink) { exists (DataFlow::Node prefix | prefix = prefixCandidate(sink) and - not exists(getPrefixOperand(prefix)) and + not exists(StringConcatenation::getFirstOperand(prefix)) and not exists(prefix.getAPredecessor()) and result = prefix.asExpr() ) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UrlConcatenation.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UrlConcatenation.qll index 80be600def2..5d12531128a 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/UrlConcatenation.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UrlConcatenation.qll @@ -11,16 +11,13 @@ import javascript * `nd` or one of its operands, assuming that it is a concatenation. */ private predicate hasSanitizingSubstring(DataFlow::Node nd) { - exists (Expr e | e = nd.asExpr() | - (e instanceof AddExpr or e instanceof AssignAddExpr) and - hasSanitizingSubstring(DataFlow::valueNode(e.getAChildExpr())) - or - e.getStringValue().regexpMatch(".*[?#].*") - ) + nd.asExpr().getStringValue().regexpMatch(".*[?#].*") or - nd.isIncomplete(_) + hasSanitizingSubstring(StringConcatenation::getAnOperand(nd)) or hasSanitizingSubstring(nd.getAPredecessor()) + or + nd.isIncomplete(_) } /** @@ -30,17 +27,7 @@ private predicate hasSanitizingSubstring(DataFlow::Node nd) { * This is considered as a sanitizing edge for the URL redirection queries. */ predicate sanitizingPrefixEdge(DataFlow::Node source, DataFlow::Node sink) { - exists (AddExpr add, DataFlow::Node left | - source.asExpr() = add.getRightOperand() and - sink.asExpr() = add and - left.asExpr() = add.getLeftOperand() and - hasSanitizingSubstring(left) - ) - or - exists (TemplateLiteral tl, int i, DataFlow::Node elt | - source.asExpr() = tl.getElement(i) and - sink.asExpr() = tl and - elt.asExpr() = tl.getElement([0..i-1]) and - hasSanitizingSubstring(elt) - ) + exists (DataFlow::Node operator, int n | + StringConcatenation::taintStep(source, sink, operator, n) and + hasSanitizingSubstring(StringConcatenation::getOperand(operator, [0..n-1]))) } diff --git a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected new file mode 100644 index 00000000000..8d28a0c8832 --- /dev/null +++ b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected @@ -0,0 +1,28 @@ +| tst.js:3:3:3:12 | x += "two" | +| tst.js:3:8:3:12 | "two" | +| tst.js:4:3:4:3 | x | +| tst.js:4:3:4:14 | x += "three" | +| tst.js:5:3:5:3 | x | +| tst.js:5:3:5:13 | x += "four" | +| tst.js:6:10:6:10 | x | +| tst.js:12:5:12:26 | x += "o ... + "two" | +| tst.js:12:10:12:26 | "one" + y + "two" | +| tst.js:12:22:12:26 | "two" | +| tst.js:19:11:19:23 | "one" + "two" | +| tst.js:19:19:19:23 | "two" | +| tst.js:20:3:20:3 | x | +| tst.js:20:3:20:25 | x += (" ... "four") | +| tst.js:21:10:21:10 | x | +| tst.js:21:10:21:19 | x + "five" | +| tst.js:25:10:25:41 | ["one", ... oin("") | +| tst.js:25:18:25:22 | "two" | +| tst.js:29:10:29:46 | Array(" ... oin("") | +| tst.js:29:23:29:27 | "two" | +| tst.js:33:10:33:50 | new Arr ... oin("") | +| tst.js:33:27:33:31 | "two" | +| tst.js:38:11:38:15 | "two" | +| tst.js:46:23:46:27 | "two" | +| tst.js:53:10:53:34 | `one ${ ... three` | +| tst.js:53:19:53:23 | two | +| tst.js:71:14:71:18 | "two" | +| tst.js:77:23:77:27 | "two" | diff --git a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.ql b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.ql new file mode 100644 index 00000000000..2e4c558414d --- /dev/null +++ b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.ql @@ -0,0 +1,15 @@ +import javascript + +// Select all expressions whose string value contains the word "two" + +predicate containsTwo(DataFlow::Node node) { + node.asExpr().getStringValue().regexpMatch(".*two.*") + or + containsTwo(node.getAPredecessor()) + or + containsTwo(StringConcatenation::getAnOperand(node)) +} + +from Expr e +where containsTwo(e.flow()) +select e diff --git a/javascript/ql/test/library-tests/StringConcatenation/tst.js b/javascript/ql/test/library-tests/StringConcatenation/tst.js new file mode 100644 index 00000000000..f9f42c33f17 --- /dev/null +++ b/javascript/ql/test/library-tests/StringConcatenation/tst.js @@ -0,0 +1,82 @@ +function append() { + let x = "one"; + x += "two"; + x += "three" + x += "four" + return x; +} + +function appendClosure(ys) { + let x = "first"; + ys.forEach(y => { + x += "one" + y + "two"; + }); + x += "last"; + return x; +} + +function appendMixed() { + let x = "one" + "two"; + x += ("three" + "four"); + return x + "five"; +} + +function joinArrayLiteral() { + return ["one", "two", "three"].join(""); +} + +function joinArrayCall() { + return Array("one", "two", "three").join(""); +} + +function joinArrayNewCall() { + return new Array("one", "two", "three").join(""); +} + +function push() { + let xs = ["one"]; + xs.push("two"); + xs.push("three", "four"); + return xs.join(""); +} + +function pushClosure(ys) { + let xs = ["first"]; + ys.forEach(y => { + xs.push("one", y, "two"); + }); + xs.push("last"); + return xs.join(""); +} + +function template(x) { + return `one ${x} two ${x} three`; +} + +function taggedTemplate(mid) { + return someTag`first ${mid} last`; +} + +function templateRepeated(x) { + return `first ${x}${x}${x} last`; +} + +function makeArray() { + return []; +} + +function pushNoLocalCreation() { + let array = makeArray(); + array.push("one"); + array.push("two"); + array.push("three"); + return array.join(""); +} + +function joinInClosure() { + let array = ["one", "two", "three"]; + function f() { + return array.join(); + } + return f(); +} From 9384b85bcca20f3407a985c4e123360f80e3f142 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 17 Sep 2018 14:31:26 +0100 Subject: [PATCH 62/73] JavaScript: ensure prefix sanitizers work for array.join() --- .../semmle/javascript/StringConcatenation.qll | 17 +++++++++++++---- .../StringConcatenation/ContainsTwo.expected | 3 +++ .../ServerSideUrlRedirect.expected | 1 + .../CWE-601/ServerSideUrlRedirect/express.js | 11 +++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/StringConcatenation.qll b/javascript/ql/src/semmle/javascript/StringConcatenation.qll index bdbdd787133..50e06860bd0 100644 --- a/javascript/ql/src/semmle/javascript/StringConcatenation.qll +++ b/javascript/ql/src/semmle/javascript/StringConcatenation.qll @@ -28,10 +28,19 @@ module StringConcatenation { or n = 1 and result = assign.getRhs().flow()) or - exists (DataFlow::ArrayCreationNode array | - node = array.getAMethodCall("join") and - node.(DataFlow::MethodCallNode).getArgument(0).mayHaveStringValue("") and - result = array.getElement(n)) + exists (DataFlow::ArrayCreationNode array, DataFlow::MethodCallNode call | + call = array.getAMethodCall("join") and + call.getArgument(0).mayHaveStringValue("") and + ( + // step from array element to array + result = array.getElement(n) and + node = array + or + // step from array to join call + node = call and + result = array and + n = 0 + )) } /** Gets an operand to the string concatenation defining `node`. */ diff --git a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected index 8d28a0c8832..841e0fad9e3 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected @@ -14,10 +14,13 @@ | tst.js:20:3:20:25 | x += (" ... "four") | | tst.js:21:10:21:10 | x | | tst.js:21:10:21:19 | x + "five" | +| tst.js:25:10:25:32 | ["one", ... three"] | | tst.js:25:10:25:41 | ["one", ... oin("") | | tst.js:25:18:25:22 | "two" | +| tst.js:29:10:29:37 | Array(" ... three") | | tst.js:29:10:29:46 | Array(" ... oin("") | | tst.js:29:23:29:27 | "two" | +| tst.js:33:10:33:41 | new Arr ... three") | | tst.js:33:10:33:50 | new Arr ... oin("") | | tst.js:33:27:33:31 | "two" | | tst.js:38:11:38:15 | "two" | diff --git a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected index 7affeef3b05..2bf3cc7dd5b 100644 --- a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected +++ b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected @@ -6,6 +6,7 @@ | express.js:78:16:78:43 | `${req. ... )}/foo` | Untrusted URL redirection due to $@. | express.js:78:19:78:37 | req.param("target") | user-provided value | | express.js:94:18:94:23 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value | | express.js:101:16:101:21 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value | +| express.js:119:16:119:72 | [req.qu ... oin('') | Untrusted URL redirection due to $@. | express.js:119:17:119:30 | req.query.page | user-provided value | | node.js:7:34:7:39 | target | Untrusted URL redirection due to $@. | node.js:6:26:6:32 | req.url | user-provided value | | node.js:15:34:15:45 | '/' + target | Untrusted URL redirection due to $@. | node.js:11:26:11:32 | req.url | user-provided value | | node.js:32:34:32:55 | target ... =" + me | Untrusted URL redirection due to $@. | node.js:29:26:29:32 | req.url | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js index 7ae7bbb3d90..2a62c7a9e5c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js +++ b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/express.js @@ -110,3 +110,14 @@ app.get('/some/path', function(req, res) { else res.redirect(target); }); + +app.get('/array/join', function(req, res) { + // GOOD: request input embedded in query string + res.redirect(['index.html?section=', req.query.section].join('')); + + // GOOD: request input still embedded in query string + res.redirect(['index.html?section=', '34'].join('') + '&subsection=' + req.query.subsection); + + // BAD: request input becomes before query string + res.redirect([req.query.page, '?section=', req.query.section].join('')); +}); From 46b2c19c66fe022c005260eea79699ccd7fca78d Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Mon, 17 Sep 2018 17:19:05 -0700 Subject: [PATCH 63/73] C++: Initial attempt at IR-based value numbering --- config/identical-files.json | 5 + cpp/ql/src/semmle/code/cpp/ir/IR.md | 7 + cpp/ql/src/semmle/code/cpp/ir/PrintIR.qll | 1 + .../cpp/ir/implementation/aliased_ssa/IR.qll | 27 + .../aliased_ssa/Instruction.qll | 16 + .../ir/implementation/aliased_ssa/PrintIR.qll | 22 + .../aliased_ssa/internal/SSAConstruction.qll | 4 + .../aliased_ssa/internal/gvn/ValueNumber.qll | 264 +++++++ .../internal/gvn/ValueNumberInternal.qll | 1 + .../code/cpp/ir/implementation/raw/IR.qll | 27 + .../cpp/ir/implementation/raw/Instruction.qll | 16 + .../cpp/ir/implementation/raw/PrintIR.qll | 22 + .../raw/internal/IRConstruction.qll | 8 + .../raw/internal/gvn/ValueNumber.qll | 264 +++++++ .../raw/internal/gvn/ValueNumberInternal.qll | 1 + .../ir/implementation/unaliased_ssa/IR.qll | 27 + .../unaliased_ssa/Instruction.qll | 16 + .../implementation/unaliased_ssa/PrintIR.qll | 22 + .../internal/SSAConstruction.qll | 4 + .../internal/gvn/ValueNumber.qll | 264 +++++++ .../internal/gvn/ValueNumberInternal.qll | 1 + .../GlobalValueNumbering/ir_gvn.expected | 664 ++++++++++++++++++ .../GlobalValueNumbering/ir_gvn.ql | 6 + 23 files changed, 1689 insertions(+) create mode 100644 cpp/ql/src/semmle/code/cpp/ir/IR.md create mode 100644 cpp/ql/src/semmle/code/cpp/ir/PrintIR.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumberInternal.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumberInternal.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumberInternal.qll create mode 100644 cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected create mode 100644 cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql diff --git a/config/identical-files.json b/config/identical-files.json index bb1c5dca83c..c7c6ba4bc01 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -54,5 +54,10 @@ "C++ SSA SSAConstruction": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll", "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll" + ], + "C++ IR ValueNumber": [ + "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll", + "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll", + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll" ] } diff --git a/cpp/ql/src/semmle/code/cpp/ir/IR.md b/cpp/ql/src/semmle/code/cpp/ir/IR.md new file mode 100644 index 00000000000..e40e04d4a3d --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/IR.md @@ -0,0 +1,7 @@ +# The C/C++ Intermediate Representation + +## Introduction + +The Intermediate Representation (IR) library provides a representation of the semantics of the program, independent of the syntax used to express those semantics. The IR is similar in design to representations used in optimizing compilers, such as LLVM IR. + +## Memory Access diff --git a/cpp/ql/src/semmle/code/cpp/ir/PrintIR.qll b/cpp/ql/src/semmle/code/cpp/ir/PrintIR.qll new file mode 100644 index 00000000000..3ff80237635 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/PrintIR.qll @@ -0,0 +1 @@ +import implementation.aliased_ssa.PrintIR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IR.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IR.qll index 26813be359c..5e84762e7eb 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IR.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IR.qll @@ -5,3 +5,30 @@ import IRVariable import OperandTag import semmle.code.cpp.ir.implementation.EdgeKind import semmle.code.cpp.ir.implementation.MemoryAccessKind + +private newtype TIRPropertyProvider = MkIRPropertyProvider() + +/** + * Class that provides additional properties to be dumped for IR instructions and blocks when using + * the PrintIR module. Libraries that compute additional facts about IR elements can extend the + * single instance of this class to specify the additional properties computed by the library. + */ +class IRPropertyProvider extends TIRPropertyProvider { + string toString() { + result = "IRPropertyProvider" + } + + /** + * Gets the value of the property named `key` for the specified instruction. + */ + string getInstructionProperty(Instruction instruction, string key) { + none() + } + + /** + * Gets the value of the property named `key` for the specified block. + */ + string getBlockProperty(IRBlock block, string key) { + none() + } +} 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 68a3e83d2db..a7046ce467d 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 @@ -301,6 +301,13 @@ class Instruction extends Construction::TInstruction { result = ast.getLocation() } + /** + * Gets the `Expr` whose results is computed by this instruction, if any. + */ + final Expr getResultExpression() { + result = Construction::getInstructionResultExpression(this) + } + /** * Gets the type of the result produced by this instruction. If the * instruction does not produce a result, its result type will be `VoidType`. @@ -554,6 +561,15 @@ class InitializeParameterInstruction extends VariableInstruction { } } +/** + * An instruction that initializes the `this` pointer parameter of the enclosing function. + */ +class InitializeThisInstruction extends Instruction { + InitializeThisInstruction() { + opcode instanceof Opcode::InitializeThis + } +} + class FieldAddressInstruction extends FieldInstruction { FieldAddressInstruction() { opcode instanceof Opcode::FieldAddress diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/PrintIR.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/PrintIR.qll index e745b20529a..478e92fac5c 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/PrintIR.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/PrintIR.qll @@ -1,6 +1,18 @@ private import IR import cpp +private string getAdditionalInstructionProperty(Instruction instr, string key) { + exists(IRPropertyProvider provider | + result = provider.getInstructionProperty(instr, key) + ) +} + +private string getAdditionalBlockProperty(IRBlock block, string key) { + exists(IRPropertyProvider provider | + result = provider.getBlockProperty(block, key) + ) +} + private newtype TPrintableIRNode = TPrintableFunctionIR(FunctionIR funcIR) or TPrintableIRBlock(IRBlock block) or @@ -135,6 +147,11 @@ class PrintableIRBlock extends PrintableIRNode, TPrintableIRBlock { result.getFunctionIR() = block.getFunctionIR() } + override string getProperty(string key) { + result = PrintableIRNode.super.getProperty(key) or + result = getAdditionalBlockProperty(block, key) + } + final IRBlock getBlock() { result = block } @@ -185,6 +202,11 @@ class PrintableInstruction extends PrintableIRNode, TPrintableInstruction { final Instruction getInstruction() { result = instr } + + override string getProperty(string key) { + result = PrintableIRNode.super.getProperty(key) or + result = getAdditionalInstructionProperty(instr, key) + } } private predicate columnWidths(IRBlock block, int resultWidth, int operationWidth) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index aad29a8c442..4a992ffb4ce 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -195,6 +195,10 @@ cached private module Cached { ) } + cached Expr getInstructionResultExpression(Instruction instruction) { + result = getOldInstruction(instruction).getResultExpression() + } + cached Instruction getInstructionSuccessor(Instruction instruction, EdgeKind kind) { result = getNewInstruction(getOldInstruction(instruction).getSuccessor(kind)) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll new file mode 100644 index 00000000000..e682c417ba2 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll @@ -0,0 +1,264 @@ +private import ValueNumberInternal +import cpp +private import IR + +/** + * Provides additional information about value numbering in IR dumps. + */ +class ValueNumberPropertyProvider extends IRPropertyProvider { + override string getInstructionProperty(Instruction instr, string key) { + exists(ValueNumber vn | + vn = valueNumber(instr) and + key = "valnum" and + if strictcount(vn.getAnInstruction()) > 1 then + result = vn.toString() + else + result = "unique" + ) + } +} + +newtype TValueNumber = + TVariableAddressValueNumber(FunctionIR funcIR, IRVariable var) { + variableAddressValueNumber(_, funcIR, var) + } or + TInitializeParameterValueNumber(FunctionIR funcIR, IRVariable var) { + initializeParameterValueNumber(_, funcIR, var) + } or + TInitializeThisValueNumber(FunctionIR funcIR) { + initializeThisValueNumber(_, funcIR) + } or + TConstantValueNumber(FunctionIR funcIR, Type type, string value) { + constantValueNumber(_, funcIR, type, value) + } or + TFieldAddressValueNumber(FunctionIR funcIR, Field field, ValueNumber objectAddress) { + fieldAddressValueNumber(_, funcIR, field, objectAddress) + } or + TBinaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber leftOperand, + ValueNumber rightOperand) { + binaryValueNumber(_, funcIR, opcode, type, leftOperand, rightOperand) + } or + TPointerArithmeticValueNumber(FunctionIR funcIR, Opcode opcode, Type type, int elementSize, + ValueNumber leftOperand, ValueNumber rightOperand) { + pointerArithmeticValueNumber(_, funcIR, opcode, type, elementSize, leftOperand, rightOperand) + } or + TUnaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber operand) { + unaryValueNumber(_, funcIR, opcode, type, operand) + } or + TUniqueValueNumber(FunctionIR funcIR, Instruction instr) { + uniqueValueNumber(instr, funcIR) + } + +/** + * The value number assigned to a particular set of instructions that produce equivalent results. + */ +class ValueNumber extends TValueNumber { + final string toString() { + result = getExampleInstruction().getResultId() + } + + final Location getLocation() { + result = getExampleInstruction().getLocation() + } + + /** + * Gets the instructions that have been assigned this value number. This will always produce at + * least one result. + */ + final Instruction getAnInstruction() { + this = valueNumber(result) + } + + /** + * Gets one of the instructions that was assigned this value number. The chosen instuction is + * deterministic but arbitrary. Intended for use only in debugging. + */ + final Instruction getExampleInstruction() { + result = min(Instruction instr | + instr = getAnInstruction() | + instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock() + ) + } +} + +class BinaryValueNumber extends ValueNumber, TBinaryValueNumber {} +class UnaryValueNumber extends ValueNumber, TUnaryValueNumber {} +class ConstantValueNumber extends ValueNumber, TConstantValueNumber {} +class FieldAddressValueNumber extends ValueNumber, TFieldAddressValueNumber {} +class PointerArithmeticValueNumber extends ValueNumber, TPointerArithmeticValueNumber {} +class UniqueValueNumber extends ValueNumber, TUniqueValueNumber {} + +/** + * A `CopyInstruction` whose source operand's value is congruent to the definition of that source + * operand. + * For example: + * ``` + * Point p = { 1, 2 }; + * Point q = p; + * int a = p.x; + * ``` + * The use of `p` on line 2 is linked to the definition of `p` on line 1, and is congruent to that + * definition because it accesses the exact same memory. + * The use of `p.x` on line 3 is linked to the definition of `p` on line 1 as well, but is not + * congruent to that definition because `p.x` accesses only a subset of the memory defined by `p`. + * + * This concept should probably be exposed in the public IR API. + */ +private class CongruentCopyInstruction extends CopyInstruction { + CongruentCopyInstruction() { + exists(Instruction def | + def = this.getSourceValue() and + ( + def.getResultMemoryAccess() instanceof IndirectMemoryAccess or + not def.hasMemoryResult() + ) + ) + } +} + +/** + * Holds if this library knows how to assign a value number to the specified instruction, other than + * a `unique` value number that is never shared by multiple instructions. + */ +private predicate numberableInstruction(Instruction instr) { + instr instanceof VariableAddressInstruction or + instr instanceof InitializeParameterInstruction or + instr instanceof InitializeThisInstruction or + instr instanceof ConstantInstruction or + instr instanceof FieldAddressInstruction or + instr instanceof BinaryInstruction or + instr instanceof UnaryInstruction or + instr instanceof PointerArithmeticInstruction or + instr instanceof CongruentCopyInstruction +} + +private predicate variableAddressValueNumber(VariableAddressInstruction instr, FunctionIR funcIR, + IRVariable var) { + instr.getFunctionIR() = funcIR and + instr.getVariable() = var +} + +private predicate initializeParameterValueNumber(InitializeParameterInstruction instr, + FunctionIR funcIR, IRVariable var) { + instr.getFunctionIR() = funcIR and + instr.getVariable() = var +} + +private predicate initializeThisValueNumber(InitializeThisInstruction instr, FunctionIR funcIR) { + instr.getFunctionIR() = funcIR +} + +private predicate constantValueNumber(ConstantInstruction instr, FunctionIR funcIR, Type type, + string value) { + instr.getFunctionIR() = funcIR and + instr.getResultType() = type and + instr.getValue() = value +} + +private predicate fieldAddressValueNumber(FieldAddressInstruction instr, FunctionIR funcIR, + Field field, ValueNumber objectAddress) { + instr.getFunctionIR() = funcIR and + instr.getField() = field and + valueNumber(instr.getObjectAddress()) = objectAddress +} + +private predicate binaryValueNumber(BinaryInstruction instr, FunctionIR funcIR, Opcode opcode, + Type type, ValueNumber leftOperand, ValueNumber rightOperand) { + instr.getFunctionIR() = funcIR and + (not instr instanceof PointerArithmeticInstruction) and + instr.getOpcode() = opcode and + instr.getResultType() = type and + valueNumber(instr.getLeftOperand()) = leftOperand and + valueNumber(instr.getRightOperand()) = rightOperand +} + +private predicate pointerArithmeticValueNumber(PointerArithmeticInstruction instr, + FunctionIR funcIR, Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, + ValueNumber rightOperand) { + instr.getFunctionIR() = funcIR and + instr.getOpcode() = opcode and + instr.getResultType() = type and + instr.getElementSize() = elementSize and + valueNumber(instr.getLeftOperand()) = leftOperand and + valueNumber(instr.getRightOperand()) = rightOperand +} + +private predicate unaryValueNumber(UnaryInstruction instr, FunctionIR funcIR, Opcode opcode, + Type type, ValueNumber operand) { + instr.getFunctionIR() = funcIR and + (not instr instanceof InheritanceConversionInstruction) and + (not instr instanceof FieldAddressInstruction) and + instr.getOpcode() = opcode and + instr.getResultType() = type and + valueNumber(instr.getOperand()) = operand +} + +/** + * Holds if `instr` should be assigned a unique value number because this library does not know how + * to determine if two instances of that instruction are equivalent. + */ +private predicate uniqueValueNumber(Instruction instr, FunctionIR funcIR) { + instr.getFunctionIR() = funcIR and + (not instr.getResultType() instanceof VoidType) and + not numberableInstruction(instr) +} + +/** + * Gets the value number assigned to `instr`, if any. Returns at most one result. + */ +ValueNumber valueNumber(Instruction instr) { + result = nonUniqueValueNumber(instr) or + exists(FunctionIR funcIR | + uniqueValueNumber(instr, funcIR) and + result = TUniqueValueNumber(funcIR, instr) + ) +} + +/** + * Gets the value number assigned to `instr`, if any, unless that instruction is assigned a unique + * value number. + */ +private ValueNumber nonUniqueValueNumber(Instruction instr) { + exists(FunctionIR funcIR | + funcIR = instr.getFunctionIR() and + ( + exists(IRVariable var | + variableAddressValueNumber(instr, funcIR, var) and + result = TVariableAddressValueNumber(funcIR, var) + ) or + exists(IRVariable var | + initializeParameterValueNumber(instr, funcIR, var) and + result = TInitializeParameterValueNumber(funcIR, var) + ) or + ( + initializeThisValueNumber(instr, funcIR) and + result = TInitializeThisValueNumber(funcIR) + ) or + exists(Type type, string value | + constantValueNumber(instr, funcIR, type, value) and + result = TConstantValueNumber(funcIR, type, value) + ) or + exists(Field field, ValueNumber objectAddress | + fieldAddressValueNumber(instr, funcIR, field, objectAddress) and + result = TFieldAddressValueNumber(funcIR, field, objectAddress) + ) or + exists(Opcode opcode, Type type, ValueNumber leftOperand, ValueNumber rightOperand | + binaryValueNumber(instr, funcIR, opcode, type, leftOperand, rightOperand) and + result = TBinaryValueNumber(funcIR, opcode, type, leftOperand, rightOperand) + ) or + exists(Opcode opcode, Type type, ValueNumber operand | + unaryValueNumber(instr, funcIR, opcode, type, operand) and + result = TUnaryValueNumber(funcIR, opcode, type, operand) + ) or + exists(Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, + ValueNumber rightOperand | + pointerArithmeticValueNumber(instr, funcIR, opcode, type, elementSize, leftOperand, + rightOperand) and + result = TPointerArithmeticValueNumber(funcIR, opcode, type, elementSize, leftOperand, + rightOperand) + ) or + // The value number of a copy is just the value number of its source value. + result = valueNumber(instr.(CongruentCopyInstruction).getSourceValue()) + ) + ) +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumberInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumberInternal.qll new file mode 100644 index 00000000000..d55844c0471 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumberInternal.qll @@ -0,0 +1 @@ +import semmle.code.cpp.ir.implementation.aliased_ssa.IR as IR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IR.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IR.qll index 26813be359c..5e84762e7eb 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IR.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IR.qll @@ -5,3 +5,30 @@ import IRVariable import OperandTag import semmle.code.cpp.ir.implementation.EdgeKind import semmle.code.cpp.ir.implementation.MemoryAccessKind + +private newtype TIRPropertyProvider = MkIRPropertyProvider() + +/** + * Class that provides additional properties to be dumped for IR instructions and blocks when using + * the PrintIR module. Libraries that compute additional facts about IR elements can extend the + * single instance of this class to specify the additional properties computed by the library. + */ +class IRPropertyProvider extends TIRPropertyProvider { + string toString() { + result = "IRPropertyProvider" + } + + /** + * Gets the value of the property named `key` for the specified instruction. + */ + string getInstructionProperty(Instruction instruction, string key) { + none() + } + + /** + * Gets the value of the property named `key` for the specified block. + */ + string getBlockProperty(IRBlock block, string key) { + none() + } +} 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 68a3e83d2db..a7046ce467d 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 @@ -301,6 +301,13 @@ class Instruction extends Construction::TInstruction { result = ast.getLocation() } + /** + * Gets the `Expr` whose results is computed by this instruction, if any. + */ + final Expr getResultExpression() { + result = Construction::getInstructionResultExpression(this) + } + /** * Gets the type of the result produced by this instruction. If the * instruction does not produce a result, its result type will be `VoidType`. @@ -554,6 +561,15 @@ class InitializeParameterInstruction extends VariableInstruction { } } +/** + * An instruction that initializes the `this` pointer parameter of the enclosing function. + */ +class InitializeThisInstruction extends Instruction { + InitializeThisInstruction() { + opcode instanceof Opcode::InitializeThis + } +} + class FieldAddressInstruction extends FieldInstruction { FieldAddressInstruction() { opcode instanceof Opcode::FieldAddress diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/PrintIR.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/PrintIR.qll index e745b20529a..478e92fac5c 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/PrintIR.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/PrintIR.qll @@ -1,6 +1,18 @@ private import IR import cpp +private string getAdditionalInstructionProperty(Instruction instr, string key) { + exists(IRPropertyProvider provider | + result = provider.getInstructionProperty(instr, key) + ) +} + +private string getAdditionalBlockProperty(IRBlock block, string key) { + exists(IRPropertyProvider provider | + result = provider.getBlockProperty(block, key) + ) +} + private newtype TPrintableIRNode = TPrintableFunctionIR(FunctionIR funcIR) or TPrintableIRBlock(IRBlock block) or @@ -135,6 +147,11 @@ class PrintableIRBlock extends PrintableIRNode, TPrintableIRBlock { result.getFunctionIR() = block.getFunctionIR() } + override string getProperty(string key) { + result = PrintableIRNode.super.getProperty(key) or + result = getAdditionalBlockProperty(block, key) + } + final IRBlock getBlock() { result = block } @@ -185,6 +202,11 @@ class PrintableInstruction extends PrintableIRNode, TPrintableInstruction { final Instruction getInstruction() { result = instr } + + override string getProperty(string key) { + result = PrintableIRNode.super.getProperty(key) or + result = getAdditionalInstructionProperty(instr, key) + } } private predicate columnWidths(IRBlock block, int resultWidth, int operationWidth) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll index 04fe1d87b62..306180e764e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll @@ -4,6 +4,7 @@ import IRBlockConstruction as BlockConstruction private import semmle.code.cpp.ir.internal.TempVariableTag private import InstructionTag private import TranslatedElement +private import TranslatedExpr private import TranslatedFunction class InstructionTagType extends TInstructionTag { @@ -73,6 +74,13 @@ cached private module Cached { none() } + cached Expr getInstructionResultExpression(Instruction instruction) { + exists(TranslatedExpr translatedExpr | + translatedExpr = getTranslatedExpr(result) and + instruction = translatedExpr.getResult() + ) + } + cached Instruction getInstructionOperand(Instruction instruction, OperandTag tag) { result = getInstructionTranslatedElement(instruction).getInstructionOperand( instruction.getTag(), tag) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll new file mode 100644 index 00000000000..e682c417ba2 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll @@ -0,0 +1,264 @@ +private import ValueNumberInternal +import cpp +private import IR + +/** + * Provides additional information about value numbering in IR dumps. + */ +class ValueNumberPropertyProvider extends IRPropertyProvider { + override string getInstructionProperty(Instruction instr, string key) { + exists(ValueNumber vn | + vn = valueNumber(instr) and + key = "valnum" and + if strictcount(vn.getAnInstruction()) > 1 then + result = vn.toString() + else + result = "unique" + ) + } +} + +newtype TValueNumber = + TVariableAddressValueNumber(FunctionIR funcIR, IRVariable var) { + variableAddressValueNumber(_, funcIR, var) + } or + TInitializeParameterValueNumber(FunctionIR funcIR, IRVariable var) { + initializeParameterValueNumber(_, funcIR, var) + } or + TInitializeThisValueNumber(FunctionIR funcIR) { + initializeThisValueNumber(_, funcIR) + } or + TConstantValueNumber(FunctionIR funcIR, Type type, string value) { + constantValueNumber(_, funcIR, type, value) + } or + TFieldAddressValueNumber(FunctionIR funcIR, Field field, ValueNumber objectAddress) { + fieldAddressValueNumber(_, funcIR, field, objectAddress) + } or + TBinaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber leftOperand, + ValueNumber rightOperand) { + binaryValueNumber(_, funcIR, opcode, type, leftOperand, rightOperand) + } or + TPointerArithmeticValueNumber(FunctionIR funcIR, Opcode opcode, Type type, int elementSize, + ValueNumber leftOperand, ValueNumber rightOperand) { + pointerArithmeticValueNumber(_, funcIR, opcode, type, elementSize, leftOperand, rightOperand) + } or + TUnaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber operand) { + unaryValueNumber(_, funcIR, opcode, type, operand) + } or + TUniqueValueNumber(FunctionIR funcIR, Instruction instr) { + uniqueValueNumber(instr, funcIR) + } + +/** + * The value number assigned to a particular set of instructions that produce equivalent results. + */ +class ValueNumber extends TValueNumber { + final string toString() { + result = getExampleInstruction().getResultId() + } + + final Location getLocation() { + result = getExampleInstruction().getLocation() + } + + /** + * Gets the instructions that have been assigned this value number. This will always produce at + * least one result. + */ + final Instruction getAnInstruction() { + this = valueNumber(result) + } + + /** + * Gets one of the instructions that was assigned this value number. The chosen instuction is + * deterministic but arbitrary. Intended for use only in debugging. + */ + final Instruction getExampleInstruction() { + result = min(Instruction instr | + instr = getAnInstruction() | + instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock() + ) + } +} + +class BinaryValueNumber extends ValueNumber, TBinaryValueNumber {} +class UnaryValueNumber extends ValueNumber, TUnaryValueNumber {} +class ConstantValueNumber extends ValueNumber, TConstantValueNumber {} +class FieldAddressValueNumber extends ValueNumber, TFieldAddressValueNumber {} +class PointerArithmeticValueNumber extends ValueNumber, TPointerArithmeticValueNumber {} +class UniqueValueNumber extends ValueNumber, TUniqueValueNumber {} + +/** + * A `CopyInstruction` whose source operand's value is congruent to the definition of that source + * operand. + * For example: + * ``` + * Point p = { 1, 2 }; + * Point q = p; + * int a = p.x; + * ``` + * The use of `p` on line 2 is linked to the definition of `p` on line 1, and is congruent to that + * definition because it accesses the exact same memory. + * The use of `p.x` on line 3 is linked to the definition of `p` on line 1 as well, but is not + * congruent to that definition because `p.x` accesses only a subset of the memory defined by `p`. + * + * This concept should probably be exposed in the public IR API. + */ +private class CongruentCopyInstruction extends CopyInstruction { + CongruentCopyInstruction() { + exists(Instruction def | + def = this.getSourceValue() and + ( + def.getResultMemoryAccess() instanceof IndirectMemoryAccess or + not def.hasMemoryResult() + ) + ) + } +} + +/** + * Holds if this library knows how to assign a value number to the specified instruction, other than + * a `unique` value number that is never shared by multiple instructions. + */ +private predicate numberableInstruction(Instruction instr) { + instr instanceof VariableAddressInstruction or + instr instanceof InitializeParameterInstruction or + instr instanceof InitializeThisInstruction or + instr instanceof ConstantInstruction or + instr instanceof FieldAddressInstruction or + instr instanceof BinaryInstruction or + instr instanceof UnaryInstruction or + instr instanceof PointerArithmeticInstruction or + instr instanceof CongruentCopyInstruction +} + +private predicate variableAddressValueNumber(VariableAddressInstruction instr, FunctionIR funcIR, + IRVariable var) { + instr.getFunctionIR() = funcIR and + instr.getVariable() = var +} + +private predicate initializeParameterValueNumber(InitializeParameterInstruction instr, + FunctionIR funcIR, IRVariable var) { + instr.getFunctionIR() = funcIR and + instr.getVariable() = var +} + +private predicate initializeThisValueNumber(InitializeThisInstruction instr, FunctionIR funcIR) { + instr.getFunctionIR() = funcIR +} + +private predicate constantValueNumber(ConstantInstruction instr, FunctionIR funcIR, Type type, + string value) { + instr.getFunctionIR() = funcIR and + instr.getResultType() = type and + instr.getValue() = value +} + +private predicate fieldAddressValueNumber(FieldAddressInstruction instr, FunctionIR funcIR, + Field field, ValueNumber objectAddress) { + instr.getFunctionIR() = funcIR and + instr.getField() = field and + valueNumber(instr.getObjectAddress()) = objectAddress +} + +private predicate binaryValueNumber(BinaryInstruction instr, FunctionIR funcIR, Opcode opcode, + Type type, ValueNumber leftOperand, ValueNumber rightOperand) { + instr.getFunctionIR() = funcIR and + (not instr instanceof PointerArithmeticInstruction) and + instr.getOpcode() = opcode and + instr.getResultType() = type and + valueNumber(instr.getLeftOperand()) = leftOperand and + valueNumber(instr.getRightOperand()) = rightOperand +} + +private predicate pointerArithmeticValueNumber(PointerArithmeticInstruction instr, + FunctionIR funcIR, Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, + ValueNumber rightOperand) { + instr.getFunctionIR() = funcIR and + instr.getOpcode() = opcode and + instr.getResultType() = type and + instr.getElementSize() = elementSize and + valueNumber(instr.getLeftOperand()) = leftOperand and + valueNumber(instr.getRightOperand()) = rightOperand +} + +private predicate unaryValueNumber(UnaryInstruction instr, FunctionIR funcIR, Opcode opcode, + Type type, ValueNumber operand) { + instr.getFunctionIR() = funcIR and + (not instr instanceof InheritanceConversionInstruction) and + (not instr instanceof FieldAddressInstruction) and + instr.getOpcode() = opcode and + instr.getResultType() = type and + valueNumber(instr.getOperand()) = operand +} + +/** + * Holds if `instr` should be assigned a unique value number because this library does not know how + * to determine if two instances of that instruction are equivalent. + */ +private predicate uniqueValueNumber(Instruction instr, FunctionIR funcIR) { + instr.getFunctionIR() = funcIR and + (not instr.getResultType() instanceof VoidType) and + not numberableInstruction(instr) +} + +/** + * Gets the value number assigned to `instr`, if any. Returns at most one result. + */ +ValueNumber valueNumber(Instruction instr) { + result = nonUniqueValueNumber(instr) or + exists(FunctionIR funcIR | + uniqueValueNumber(instr, funcIR) and + result = TUniqueValueNumber(funcIR, instr) + ) +} + +/** + * Gets the value number assigned to `instr`, if any, unless that instruction is assigned a unique + * value number. + */ +private ValueNumber nonUniqueValueNumber(Instruction instr) { + exists(FunctionIR funcIR | + funcIR = instr.getFunctionIR() and + ( + exists(IRVariable var | + variableAddressValueNumber(instr, funcIR, var) and + result = TVariableAddressValueNumber(funcIR, var) + ) or + exists(IRVariable var | + initializeParameterValueNumber(instr, funcIR, var) and + result = TInitializeParameterValueNumber(funcIR, var) + ) or + ( + initializeThisValueNumber(instr, funcIR) and + result = TInitializeThisValueNumber(funcIR) + ) or + exists(Type type, string value | + constantValueNumber(instr, funcIR, type, value) and + result = TConstantValueNumber(funcIR, type, value) + ) or + exists(Field field, ValueNumber objectAddress | + fieldAddressValueNumber(instr, funcIR, field, objectAddress) and + result = TFieldAddressValueNumber(funcIR, field, objectAddress) + ) or + exists(Opcode opcode, Type type, ValueNumber leftOperand, ValueNumber rightOperand | + binaryValueNumber(instr, funcIR, opcode, type, leftOperand, rightOperand) and + result = TBinaryValueNumber(funcIR, opcode, type, leftOperand, rightOperand) + ) or + exists(Opcode opcode, Type type, ValueNumber operand | + unaryValueNumber(instr, funcIR, opcode, type, operand) and + result = TUnaryValueNumber(funcIR, opcode, type, operand) + ) or + exists(Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, + ValueNumber rightOperand | + pointerArithmeticValueNumber(instr, funcIR, opcode, type, elementSize, leftOperand, + rightOperand) and + result = TPointerArithmeticValueNumber(funcIR, opcode, type, elementSize, leftOperand, + rightOperand) + ) or + // The value number of a copy is just the value number of its source value. + result = valueNumber(instr.(CongruentCopyInstruction).getSourceValue()) + ) + ) +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumberInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumberInternal.qll new file mode 100644 index 00000000000..3b28a05290c --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumberInternal.qll @@ -0,0 +1 @@ +import semmle.code.cpp.ir.implementation.raw.IR as IR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IR.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IR.qll index 26813be359c..5e84762e7eb 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IR.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IR.qll @@ -5,3 +5,30 @@ import IRVariable import OperandTag import semmle.code.cpp.ir.implementation.EdgeKind import semmle.code.cpp.ir.implementation.MemoryAccessKind + +private newtype TIRPropertyProvider = MkIRPropertyProvider() + +/** + * Class that provides additional properties to be dumped for IR instructions and blocks when using + * the PrintIR module. Libraries that compute additional facts about IR elements can extend the + * single instance of this class to specify the additional properties computed by the library. + */ +class IRPropertyProvider extends TIRPropertyProvider { + string toString() { + result = "IRPropertyProvider" + } + + /** + * Gets the value of the property named `key` for the specified instruction. + */ + string getInstructionProperty(Instruction instruction, string key) { + none() + } + + /** + * Gets the value of the property named `key` for the specified block. + */ + string getBlockProperty(IRBlock block, string key) { + none() + } +} 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 68a3e83d2db..a7046ce467d 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 @@ -301,6 +301,13 @@ class Instruction extends Construction::TInstruction { result = ast.getLocation() } + /** + * Gets the `Expr` whose results is computed by this instruction, if any. + */ + final Expr getResultExpression() { + result = Construction::getInstructionResultExpression(this) + } + /** * Gets the type of the result produced by this instruction. If the * instruction does not produce a result, its result type will be `VoidType`. @@ -554,6 +561,15 @@ class InitializeParameterInstruction extends VariableInstruction { } } +/** + * An instruction that initializes the `this` pointer parameter of the enclosing function. + */ +class InitializeThisInstruction extends Instruction { + InitializeThisInstruction() { + opcode instanceof Opcode::InitializeThis + } +} + class FieldAddressInstruction extends FieldInstruction { FieldAddressInstruction() { opcode instanceof Opcode::FieldAddress diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/PrintIR.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/PrintIR.qll index e745b20529a..478e92fac5c 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/PrintIR.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/PrintIR.qll @@ -1,6 +1,18 @@ private import IR import cpp +private string getAdditionalInstructionProperty(Instruction instr, string key) { + exists(IRPropertyProvider provider | + result = provider.getInstructionProperty(instr, key) + ) +} + +private string getAdditionalBlockProperty(IRBlock block, string key) { + exists(IRPropertyProvider provider | + result = provider.getBlockProperty(block, key) + ) +} + private newtype TPrintableIRNode = TPrintableFunctionIR(FunctionIR funcIR) or TPrintableIRBlock(IRBlock block) or @@ -135,6 +147,11 @@ class PrintableIRBlock extends PrintableIRNode, TPrintableIRBlock { result.getFunctionIR() = block.getFunctionIR() } + override string getProperty(string key) { + result = PrintableIRNode.super.getProperty(key) or + result = getAdditionalBlockProperty(block, key) + } + final IRBlock getBlock() { result = block } @@ -185,6 +202,11 @@ class PrintableInstruction extends PrintableIRNode, TPrintableInstruction { final Instruction getInstruction() { result = instr } + + override string getProperty(string key) { + result = PrintableIRNode.super.getProperty(key) or + result = getAdditionalInstructionProperty(instr, key) + } } private predicate columnWidths(IRBlock block, int resultWidth, int operationWidth) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index aad29a8c442..4a992ffb4ce 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -195,6 +195,10 @@ cached private module Cached { ) } + cached Expr getInstructionResultExpression(Instruction instruction) { + result = getOldInstruction(instruction).getResultExpression() + } + cached Instruction getInstructionSuccessor(Instruction instruction, EdgeKind kind) { result = getNewInstruction(getOldInstruction(instruction).getSuccessor(kind)) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll new file mode 100644 index 00000000000..e682c417ba2 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll @@ -0,0 +1,264 @@ +private import ValueNumberInternal +import cpp +private import IR + +/** + * Provides additional information about value numbering in IR dumps. + */ +class ValueNumberPropertyProvider extends IRPropertyProvider { + override string getInstructionProperty(Instruction instr, string key) { + exists(ValueNumber vn | + vn = valueNumber(instr) and + key = "valnum" and + if strictcount(vn.getAnInstruction()) > 1 then + result = vn.toString() + else + result = "unique" + ) + } +} + +newtype TValueNumber = + TVariableAddressValueNumber(FunctionIR funcIR, IRVariable var) { + variableAddressValueNumber(_, funcIR, var) + } or + TInitializeParameterValueNumber(FunctionIR funcIR, IRVariable var) { + initializeParameterValueNumber(_, funcIR, var) + } or + TInitializeThisValueNumber(FunctionIR funcIR) { + initializeThisValueNumber(_, funcIR) + } or + TConstantValueNumber(FunctionIR funcIR, Type type, string value) { + constantValueNumber(_, funcIR, type, value) + } or + TFieldAddressValueNumber(FunctionIR funcIR, Field field, ValueNumber objectAddress) { + fieldAddressValueNumber(_, funcIR, field, objectAddress) + } or + TBinaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber leftOperand, + ValueNumber rightOperand) { + binaryValueNumber(_, funcIR, opcode, type, leftOperand, rightOperand) + } or + TPointerArithmeticValueNumber(FunctionIR funcIR, Opcode opcode, Type type, int elementSize, + ValueNumber leftOperand, ValueNumber rightOperand) { + pointerArithmeticValueNumber(_, funcIR, opcode, type, elementSize, leftOperand, rightOperand) + } or + TUnaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber operand) { + unaryValueNumber(_, funcIR, opcode, type, operand) + } or + TUniqueValueNumber(FunctionIR funcIR, Instruction instr) { + uniqueValueNumber(instr, funcIR) + } + +/** + * The value number assigned to a particular set of instructions that produce equivalent results. + */ +class ValueNumber extends TValueNumber { + final string toString() { + result = getExampleInstruction().getResultId() + } + + final Location getLocation() { + result = getExampleInstruction().getLocation() + } + + /** + * Gets the instructions that have been assigned this value number. This will always produce at + * least one result. + */ + final Instruction getAnInstruction() { + this = valueNumber(result) + } + + /** + * Gets one of the instructions that was assigned this value number. The chosen instuction is + * deterministic but arbitrary. Intended for use only in debugging. + */ + final Instruction getExampleInstruction() { + result = min(Instruction instr | + instr = getAnInstruction() | + instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock() + ) + } +} + +class BinaryValueNumber extends ValueNumber, TBinaryValueNumber {} +class UnaryValueNumber extends ValueNumber, TUnaryValueNumber {} +class ConstantValueNumber extends ValueNumber, TConstantValueNumber {} +class FieldAddressValueNumber extends ValueNumber, TFieldAddressValueNumber {} +class PointerArithmeticValueNumber extends ValueNumber, TPointerArithmeticValueNumber {} +class UniqueValueNumber extends ValueNumber, TUniqueValueNumber {} + +/** + * A `CopyInstruction` whose source operand's value is congruent to the definition of that source + * operand. + * For example: + * ``` + * Point p = { 1, 2 }; + * Point q = p; + * int a = p.x; + * ``` + * The use of `p` on line 2 is linked to the definition of `p` on line 1, and is congruent to that + * definition because it accesses the exact same memory. + * The use of `p.x` on line 3 is linked to the definition of `p` on line 1 as well, but is not + * congruent to that definition because `p.x` accesses only a subset of the memory defined by `p`. + * + * This concept should probably be exposed in the public IR API. + */ +private class CongruentCopyInstruction extends CopyInstruction { + CongruentCopyInstruction() { + exists(Instruction def | + def = this.getSourceValue() and + ( + def.getResultMemoryAccess() instanceof IndirectMemoryAccess or + not def.hasMemoryResult() + ) + ) + } +} + +/** + * Holds if this library knows how to assign a value number to the specified instruction, other than + * a `unique` value number that is never shared by multiple instructions. + */ +private predicate numberableInstruction(Instruction instr) { + instr instanceof VariableAddressInstruction or + instr instanceof InitializeParameterInstruction or + instr instanceof InitializeThisInstruction or + instr instanceof ConstantInstruction or + instr instanceof FieldAddressInstruction or + instr instanceof BinaryInstruction or + instr instanceof UnaryInstruction or + instr instanceof PointerArithmeticInstruction or + instr instanceof CongruentCopyInstruction +} + +private predicate variableAddressValueNumber(VariableAddressInstruction instr, FunctionIR funcIR, + IRVariable var) { + instr.getFunctionIR() = funcIR and + instr.getVariable() = var +} + +private predicate initializeParameterValueNumber(InitializeParameterInstruction instr, + FunctionIR funcIR, IRVariable var) { + instr.getFunctionIR() = funcIR and + instr.getVariable() = var +} + +private predicate initializeThisValueNumber(InitializeThisInstruction instr, FunctionIR funcIR) { + instr.getFunctionIR() = funcIR +} + +private predicate constantValueNumber(ConstantInstruction instr, FunctionIR funcIR, Type type, + string value) { + instr.getFunctionIR() = funcIR and + instr.getResultType() = type and + instr.getValue() = value +} + +private predicate fieldAddressValueNumber(FieldAddressInstruction instr, FunctionIR funcIR, + Field field, ValueNumber objectAddress) { + instr.getFunctionIR() = funcIR and + instr.getField() = field and + valueNumber(instr.getObjectAddress()) = objectAddress +} + +private predicate binaryValueNumber(BinaryInstruction instr, FunctionIR funcIR, Opcode opcode, + Type type, ValueNumber leftOperand, ValueNumber rightOperand) { + instr.getFunctionIR() = funcIR and + (not instr instanceof PointerArithmeticInstruction) and + instr.getOpcode() = opcode and + instr.getResultType() = type and + valueNumber(instr.getLeftOperand()) = leftOperand and + valueNumber(instr.getRightOperand()) = rightOperand +} + +private predicate pointerArithmeticValueNumber(PointerArithmeticInstruction instr, + FunctionIR funcIR, Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, + ValueNumber rightOperand) { + instr.getFunctionIR() = funcIR and + instr.getOpcode() = opcode and + instr.getResultType() = type and + instr.getElementSize() = elementSize and + valueNumber(instr.getLeftOperand()) = leftOperand and + valueNumber(instr.getRightOperand()) = rightOperand +} + +private predicate unaryValueNumber(UnaryInstruction instr, FunctionIR funcIR, Opcode opcode, + Type type, ValueNumber operand) { + instr.getFunctionIR() = funcIR and + (not instr instanceof InheritanceConversionInstruction) and + (not instr instanceof FieldAddressInstruction) and + instr.getOpcode() = opcode and + instr.getResultType() = type and + valueNumber(instr.getOperand()) = operand +} + +/** + * Holds if `instr` should be assigned a unique value number because this library does not know how + * to determine if two instances of that instruction are equivalent. + */ +private predicate uniqueValueNumber(Instruction instr, FunctionIR funcIR) { + instr.getFunctionIR() = funcIR and + (not instr.getResultType() instanceof VoidType) and + not numberableInstruction(instr) +} + +/** + * Gets the value number assigned to `instr`, if any. Returns at most one result. + */ +ValueNumber valueNumber(Instruction instr) { + result = nonUniqueValueNumber(instr) or + exists(FunctionIR funcIR | + uniqueValueNumber(instr, funcIR) and + result = TUniqueValueNumber(funcIR, instr) + ) +} + +/** + * Gets the value number assigned to `instr`, if any, unless that instruction is assigned a unique + * value number. + */ +private ValueNumber nonUniqueValueNumber(Instruction instr) { + exists(FunctionIR funcIR | + funcIR = instr.getFunctionIR() and + ( + exists(IRVariable var | + variableAddressValueNumber(instr, funcIR, var) and + result = TVariableAddressValueNumber(funcIR, var) + ) or + exists(IRVariable var | + initializeParameterValueNumber(instr, funcIR, var) and + result = TInitializeParameterValueNumber(funcIR, var) + ) or + ( + initializeThisValueNumber(instr, funcIR) and + result = TInitializeThisValueNumber(funcIR) + ) or + exists(Type type, string value | + constantValueNumber(instr, funcIR, type, value) and + result = TConstantValueNumber(funcIR, type, value) + ) or + exists(Field field, ValueNumber objectAddress | + fieldAddressValueNumber(instr, funcIR, field, objectAddress) and + result = TFieldAddressValueNumber(funcIR, field, objectAddress) + ) or + exists(Opcode opcode, Type type, ValueNumber leftOperand, ValueNumber rightOperand | + binaryValueNumber(instr, funcIR, opcode, type, leftOperand, rightOperand) and + result = TBinaryValueNumber(funcIR, opcode, type, leftOperand, rightOperand) + ) or + exists(Opcode opcode, Type type, ValueNumber operand | + unaryValueNumber(instr, funcIR, opcode, type, operand) and + result = TUnaryValueNumber(funcIR, opcode, type, operand) + ) or + exists(Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, + ValueNumber rightOperand | + pointerArithmeticValueNumber(instr, funcIR, opcode, type, elementSize, leftOperand, + rightOperand) and + result = TPointerArithmeticValueNumber(funcIR, opcode, type, elementSize, leftOperand, + rightOperand) + ) or + // The value number of a copy is just the value number of its source value. + result = valueNumber(instr.(CongruentCopyInstruction).getSourceValue()) + ) + ) +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumberInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumberInternal.qll new file mode 100644 index 00000000000..9b4f813a10b --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumberInternal.qll @@ -0,0 +1 @@ +import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as IR diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected new file mode 100644 index 00000000000..479df8570b8 --- /dev/null +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected @@ -0,0 +1,664 @@ +test.cpp: +# 1| test00(int, int) -> int +# 1| Block 0 +# 1| v0_0(void) = EnterFunction : +# 1| mu0_1(unknown) = UnmodeledDefinition : +# 1| valnum = unique +# 1| r0_2(glval) = VariableAddress[p0] : +# 1| valnum = r0_2 +# 1| m0_3(int) = InitializeParameter[p0] : r0_2 +# 1| valnum = m0_3 +# 1| r0_4(glval) = VariableAddress[p1] : +# 1| valnum = r0_4 +# 1| m0_5(int) = InitializeParameter[p1] : r0_4 +# 1| valnum = m0_5 +# 2| r0_6(glval) = VariableAddress[x] : +# 2| valnum = r0_6 +# 2| m0_7(int) = Uninitialized : r0_6 +# 2| valnum = unique +# 2| r0_8(glval) = VariableAddress[y] : +# 2| valnum = r0_8 +# 2| m0_9(int) = Uninitialized : r0_8 +# 2| valnum = unique +# 3| r0_10(glval) = VariableAddress[b] : +# 3| valnum = unique +# 3| m0_11(unsigned char) = Uninitialized : r0_10 +# 3| valnum = unique +# 5| r0_12(glval) = VariableAddress[p0] : +# 5| valnum = r0_2 +# 5| r0_13(int) = Load : r0_12, m0_3 +# 5| valnum = m0_3 +# 5| r0_14(glval) = VariableAddress[p1] : +# 5| valnum = r0_4 +# 5| r0_15(int) = Load : r0_14, m0_5 +# 5| valnum = m0_5 +# 5| r0_16(int) = Add : r0_13, r0_15 +# 5| valnum = r0_16 +# 5| r0_17(glval) = VariableAddress[x] : +# 5| valnum = r0_6 +# 5| m0_18(int) = Store : r0_17, r0_16 +# 5| valnum = r0_16 +# 6| r0_19(glval) = VariableAddress[p0] : +# 6| valnum = r0_2 +# 6| r0_20(int) = Load : r0_19, m0_3 +# 6| valnum = m0_3 +# 6| r0_21(glval) = VariableAddress[p1] : +# 6| valnum = r0_4 +# 6| r0_22(int) = Load : r0_21, m0_5 +# 6| valnum = m0_5 +# 6| r0_23(int) = Add : r0_20, r0_22 +# 6| valnum = r0_16 +# 6| r0_24(glval) = VariableAddress[x] : +# 6| valnum = r0_6 +# 6| m0_25(int) = Store : r0_24, r0_23 +# 6| valnum = r0_16 +# 7| r0_26(glval) = VariableAddress[x] : +# 7| valnum = r0_6 +# 7| r0_27(int) = Load : r0_26, m0_25 +# 7| valnum = r0_16 +# 7| r0_28(glval) = VariableAddress[y] : +# 7| valnum = r0_8 +# 7| m0_29(int) = Store : r0_28, r0_27 +# 7| valnum = r0_16 +# 8| v0_30(void) = NoOp : +# 1| r0_31(glval) = VariableAddress[#return] : +# 1| valnum = unique +# 1| v0_32(void) = ReturnValue : r0_31 +# 1| v0_33(void) = UnmodeledUse : mu* +# 1| v0_34(void) = ExitFunction : + +# 53| 1644 +# 53| valnum = unique + +# 56| 1646 +# 56| valnum = unique + +# 88| 1755 +# 88| valnum = unique + +# 12| test01(int, int) -> int +# 12| Block 0 +# 12| v0_0(void) = EnterFunction : +# 12| mu0_1(unknown) = UnmodeledDefinition : +# 12| valnum = unique +# 12| r0_2(glval) = VariableAddress[p0] : +# 12| valnum = r0_2 +# 12| m0_3(int) = InitializeParameter[p0] : r0_2 +# 12| valnum = m0_3 +# 12| r0_4(glval) = VariableAddress[p1] : +# 12| valnum = r0_4 +# 12| m0_5(int) = InitializeParameter[p1] : r0_4 +# 12| valnum = m0_5 +# 13| r0_6(glval) = VariableAddress[x] : +# 13| valnum = r0_6 +# 13| m0_7(int) = Uninitialized : r0_6 +# 13| valnum = unique +# 13| r0_8(glval) = VariableAddress[y] : +# 13| valnum = r0_8 +# 13| m0_9(int) = Uninitialized : r0_8 +# 13| valnum = unique +# 14| r0_10(glval) = VariableAddress[b] : +# 14| valnum = unique +# 14| m0_11(unsigned char) = Uninitialized : r0_10 +# 14| valnum = unique +# 16| r0_12(glval) = VariableAddress[p0] : +# 16| valnum = r0_2 +# 16| r0_13(int) = Load : r0_12, m0_3 +# 16| valnum = m0_3 +# 16| r0_14(glval) = VariableAddress[p1] : +# 16| valnum = r0_4 +# 16| r0_15(int) = Load : r0_14, m0_5 +# 16| valnum = m0_5 +# 16| r0_16(int) = Add : r0_13, r0_15 +# 16| valnum = r0_16 +# 16| r0_17(glval) = VariableAddress[global01] : +# 16| valnum = r0_17 +# 16| r0_18(int) = Load : r0_17, mu0_1 +# 16| valnum = unique +# 16| r0_19(int) = Add : r0_16, r0_18 +# 16| valnum = r0_19 +# 16| r0_20(glval) = VariableAddress[x] : +# 16| valnum = r0_6 +# 16| m0_21(int) = Store : r0_20, r0_19 +# 16| valnum = r0_19 +# 17| r0_22(glval) = VariableAddress[p0] : +# 17| valnum = r0_2 +# 17| r0_23(int) = Load : r0_22, m0_3 +# 17| valnum = m0_3 +# 17| r0_24(glval) = VariableAddress[p1] : +# 17| valnum = r0_4 +# 17| r0_25(int) = Load : r0_24, m0_5 +# 17| valnum = m0_5 +# 17| r0_26(int) = Add : r0_23, r0_25 +# 17| valnum = r0_16 +# 17| r0_27(glval) = VariableAddress[global01] : +# 17| valnum = r0_17 +# 17| r0_28(int) = Load : r0_27, mu0_1 +# 17| valnum = unique +# 17| r0_29(int) = Add : r0_26, r0_28 +# 17| valnum = r0_29 +# 17| r0_30(glval) = VariableAddress[x] : +# 17| valnum = r0_6 +# 17| m0_31(int) = Store : r0_30, r0_29 +# 17| valnum = r0_29 +# 18| r0_32(glval) = VariableAddress[x] : +# 18| valnum = r0_6 +# 18| r0_33(int) = Load : r0_32, m0_31 +# 18| valnum = r0_29 +# 18| r0_34(glval) = VariableAddress[y] : +# 18| valnum = r0_8 +# 18| m0_35(int) = Store : r0_34, r0_33 +# 18| valnum = r0_29 +# 19| v0_36(void) = NoOp : +# 12| r0_37(glval) = VariableAddress[#return] : +# 12| valnum = unique +# 12| v0_38(void) = ReturnValue : r0_37 +# 12| v0_39(void) = UnmodeledUse : mu* +# 12| v0_40(void) = ExitFunction : + +# 25| test02(int, int) -> int +# 25| Block 0 +# 25| v0_0(void) = EnterFunction : +# 25| mu0_1(unknown) = UnmodeledDefinition : +# 25| valnum = unique +# 25| r0_2(glval) = VariableAddress[p0] : +# 25| valnum = r0_2 +# 25| m0_3(int) = InitializeParameter[p0] : r0_2 +# 25| valnum = m0_3 +# 25| r0_4(glval) = VariableAddress[p1] : +# 25| valnum = r0_4 +# 25| m0_5(int) = InitializeParameter[p1] : r0_4 +# 25| valnum = m0_5 +# 26| r0_6(glval) = VariableAddress[x] : +# 26| valnum = r0_6 +# 26| m0_7(int) = Uninitialized : r0_6 +# 26| valnum = unique +# 26| r0_8(glval) = VariableAddress[y] : +# 26| valnum = r0_8 +# 26| m0_9(int) = Uninitialized : r0_8 +# 26| valnum = unique +# 27| r0_10(glval) = VariableAddress[b] : +# 27| valnum = unique +# 27| m0_11(unsigned char) = Uninitialized : r0_10 +# 27| valnum = unique +# 29| r0_12(glval) = VariableAddress[p0] : +# 29| valnum = r0_2 +# 29| r0_13(int) = Load : r0_12, m0_3 +# 29| valnum = m0_3 +# 29| r0_14(glval) = VariableAddress[p1] : +# 29| valnum = r0_4 +# 29| r0_15(int) = Load : r0_14, m0_5 +# 29| valnum = m0_5 +# 29| r0_16(int) = Add : r0_13, r0_15 +# 29| valnum = r0_16 +# 29| r0_17(glval) = VariableAddress[global02] : +# 29| valnum = r0_17 +# 29| r0_18(int) = Load : r0_17, mu0_1 +# 29| valnum = unique +# 29| r0_19(int) = Add : r0_16, r0_18 +# 29| valnum = r0_19 +# 29| r0_20(glval) = VariableAddress[x] : +# 29| valnum = r0_6 +# 29| m0_21(int) = Store : r0_20, r0_19 +# 29| valnum = r0_19 +# 30| r0_22(glval) = FunctionAddress[change_global02] : +# 30| valnum = unique +# 30| v0_23(void) = Call : r0_22 +# 31| r0_24(glval) = VariableAddress[p0] : +# 31| valnum = r0_2 +# 31| r0_25(int) = Load : r0_24, m0_3 +# 31| valnum = m0_3 +# 31| r0_26(glval) = VariableAddress[p1] : +# 31| valnum = r0_4 +# 31| r0_27(int) = Load : r0_26, m0_5 +# 31| valnum = m0_5 +# 31| r0_28(int) = Add : r0_25, r0_27 +# 31| valnum = r0_16 +# 31| r0_29(glval) = VariableAddress[global02] : +# 31| valnum = r0_17 +# 31| r0_30(int) = Load : r0_29, mu0_1 +# 31| valnum = unique +# 31| r0_31(int) = Add : r0_28, r0_30 +# 31| valnum = r0_31 +# 31| r0_32(glval) = VariableAddress[x] : +# 31| valnum = r0_6 +# 31| m0_33(int) = Store : r0_32, r0_31 +# 31| valnum = r0_31 +# 32| r0_34(glval) = VariableAddress[x] : +# 32| valnum = r0_6 +# 32| r0_35(int) = Load : r0_34, m0_33 +# 32| valnum = r0_31 +# 32| r0_36(glval) = VariableAddress[y] : +# 32| valnum = r0_8 +# 32| m0_37(int) = Store : r0_36, r0_35 +# 32| valnum = r0_31 +# 33| v0_38(void) = NoOp : +# 25| r0_39(glval) = VariableAddress[#return] : +# 25| valnum = unique +# 25| v0_40(void) = ReturnValue : r0_39 +# 25| v0_41(void) = UnmodeledUse : mu* +# 25| v0_42(void) = ExitFunction : + +# 39| test03(int, int, int *) -> int +# 39| Block 0 +# 39| v0_0(void) = EnterFunction : +# 39| mu0_1(unknown) = UnmodeledDefinition : +# 39| valnum = unique +# 39| r0_2(glval) = VariableAddress[p0] : +# 39| valnum = r0_2 +# 39| m0_3(int) = InitializeParameter[p0] : r0_2 +# 39| valnum = m0_3 +# 39| r0_4(glval) = VariableAddress[p1] : +# 39| valnum = r0_4 +# 39| m0_5(int) = InitializeParameter[p1] : r0_4 +# 39| valnum = m0_5 +# 39| r0_6(glval) = VariableAddress[p2] : +# 39| valnum = r0_6 +# 39| m0_7(int *) = InitializeParameter[p2] : r0_6 +# 39| valnum = m0_7 +# 40| r0_8(glval) = VariableAddress[x] : +# 40| valnum = r0_8 +# 40| m0_9(int) = Uninitialized : r0_8 +# 40| valnum = unique +# 40| r0_10(glval) = VariableAddress[y] : +# 40| valnum = r0_10 +# 40| m0_11(int) = Uninitialized : r0_10 +# 40| valnum = unique +# 41| r0_12(glval) = VariableAddress[b] : +# 41| valnum = unique +# 41| m0_13(unsigned char) = Uninitialized : r0_12 +# 41| valnum = unique +# 43| r0_14(glval) = VariableAddress[p0] : +# 43| valnum = r0_2 +# 43| r0_15(int) = Load : r0_14, m0_3 +# 43| valnum = m0_3 +# 43| r0_16(glval) = VariableAddress[p1] : +# 43| valnum = r0_4 +# 43| r0_17(int) = Load : r0_16, m0_5 +# 43| valnum = m0_5 +# 43| r0_18(int) = Add : r0_15, r0_17 +# 43| valnum = r0_18 +# 43| r0_19(glval) = VariableAddress[global03] : +# 43| valnum = r0_19 +# 43| r0_20(int) = Load : r0_19, mu0_1 +# 43| valnum = unique +# 43| r0_21(int) = Add : r0_18, r0_20 +# 43| valnum = r0_21 +# 43| r0_22(glval) = VariableAddress[x] : +# 43| valnum = r0_8 +# 43| m0_23(int) = Store : r0_22, r0_21 +# 43| valnum = r0_21 +# 44| r0_24(int) = Constant[0] : +# 44| valnum = r0_24 +# 44| r0_25(glval) = VariableAddress[p2] : +# 44| valnum = r0_6 +# 44| r0_26(int *) = Load : r0_25, m0_7 +# 44| valnum = m0_7 +# 44| mu0_27(int) = Store : r0_26, r0_24 +# 44| valnum = r0_24 +# 45| r0_28(glval) = VariableAddress[p0] : +# 45| valnum = r0_2 +# 45| r0_29(int) = Load : r0_28, m0_3 +# 45| valnum = m0_3 +# 45| r0_30(glval) = VariableAddress[p1] : +# 45| valnum = r0_4 +# 45| r0_31(int) = Load : r0_30, m0_5 +# 45| valnum = m0_5 +# 45| r0_32(int) = Add : r0_29, r0_31 +# 45| valnum = r0_18 +# 45| r0_33(glval) = VariableAddress[global03] : +# 45| valnum = r0_19 +# 45| r0_34(int) = Load : r0_33, mu0_1 +# 45| valnum = unique +# 45| r0_35(int) = Add : r0_32, r0_34 +# 45| valnum = r0_35 +# 45| r0_36(glval) = VariableAddress[x] : +# 45| valnum = r0_8 +# 45| m0_37(int) = Store : r0_36, r0_35 +# 45| valnum = r0_35 +# 46| r0_38(glval) = VariableAddress[x] : +# 46| valnum = r0_8 +# 46| r0_39(int) = Load : r0_38, m0_37 +# 46| valnum = r0_35 +# 46| r0_40(glval) = VariableAddress[y] : +# 46| valnum = r0_10 +# 46| m0_41(int) = Store : r0_40, r0_39 +# 46| valnum = r0_35 +# 47| v0_42(void) = NoOp : +# 39| r0_43(glval) = VariableAddress[#return] : +# 39| valnum = unique +# 39| v0_44(void) = ReturnValue : r0_43 +# 39| v0_45(void) = UnmodeledUse : mu* +# 39| v0_46(void) = ExitFunction : + +# 49| my_strspn(const char *, const char *) -> unsigned int +# 49| Block 0 +# 49| v0_0(void) = EnterFunction : +# 49| mu0_1(unknown) = UnmodeledDefinition : +# 49| valnum = unique +# 49| r0_2(glval) = VariableAddress[str] : +# 49| valnum = r0_2 +# 49| m0_3(char *) = InitializeParameter[str] : r0_2 +# 49| valnum = m0_3 +# 49| r0_4(glval) = VariableAddress[chars] : +# 49| valnum = r0_4 +# 49| m0_5(char *) = InitializeParameter[chars] : r0_4 +# 49| valnum = m0_5 +# 50| r0_6(glval) = VariableAddress[ptr] : +# 50| valnum = r0_6 +# 50| m0_7(char *) = Uninitialized : r0_6 +# 50| valnum = unique +# 51| r0_8(glval) = VariableAddress[result] : +# 51| valnum = r0_8 +# 51| r0_9(unsigned int) = Constant[0] : +# 51| valnum = r0_9 +# 51| m0_10(unsigned int) = Store : r0_8, r0_9 +# 51| valnum = r0_9 +#-----| Goto -> Block 1 + +# 53| Block 1 +# 53| m1_0(unsigned int) = Phi : from 0:m0_10, from 8:m8_4 +# 53| valnum = unique +# 53| r1_1(glval) = VariableAddress[str] : +# 53| valnum = r0_2 +# 53| r1_2(char *) = Load : r1_1, m0_3 +# 53| valnum = m0_3 +# 53| r1_3(char) = Load : r1_2, mu0_1 +# 53| valnum = unique +# 53| r1_4(int) = Convert : r1_3 +# 53| valnum = unique +# 53| r1_5(int) = Constant[0] : +# 53| valnum = r1_5 +# 53| r1_6(bool) = CompareNE : r1_4, r1_5 +# 53| valnum = unique +# 53| v1_7(void) = ConditionalBranch : r1_6 +#-----| False -> Block 9 +#-----| True -> Block 2 + +# 55| Block 2 +# 55| r2_0(glval) = VariableAddress[chars] : +# 55| valnum = r0_4 +# 55| r2_1(char *) = Load : r2_0, m0_5 +# 55| valnum = m0_5 +# 55| r2_2(glval) = VariableAddress[ptr] : +# 55| valnum = r0_6 +# 55| m2_3(char *) = Store : r2_2, r2_1 +# 55| valnum = m0_5 +#-----| Goto -> Block 3 + +# 56| Block 3 +# 56| m3_0(char *) = Phi : from 2:m2_3, from 5:m5_4 +# 56| valnum = unique +# 56| r3_1(glval) = VariableAddress[ptr] : +# 56| valnum = r0_6 +# 56| r3_2(char *) = Load : r3_1, m3_0 +# 56| valnum = unique +# 56| r3_3(char) = Load : r3_2, mu0_1 +# 56| valnum = unique +# 56| r3_4(int) = Convert : r3_3 +# 56| valnum = unique +# 56| r3_5(glval) = VariableAddress[str] : +# 56| valnum = r0_2 +# 56| r3_6(char *) = Load : r3_5, m0_3 +# 56| valnum = m0_3 +# 56| r3_7(char) = Load : r3_6, mu0_1 +# 56| valnum = unique +# 56| r3_8(int) = Convert : r3_7 +# 56| valnum = unique +# 56| r3_9(bool) = CompareNE : r3_4, r3_8 +# 56| valnum = unique +# 56| v3_10(void) = ConditionalBranch : r3_9 +#-----| False -> Block 6 +#-----| True -> Block 4 + +# 56| Block 4 +# 56| r4_0(glval) = VariableAddress[ptr] : +# 56| valnum = r0_6 +# 56| r4_1(char *) = Load : r4_0, m3_0 +# 56| valnum = unique +# 56| r4_2(char) = Load : r4_1, mu0_1 +# 56| valnum = unique +# 56| r4_3(int) = Convert : r4_2 +# 56| valnum = unique +# 56| r4_4(int) = Constant[0] : +# 56| valnum = r1_5 +# 56| r4_5(bool) = CompareNE : r4_3, r4_4 +# 56| valnum = unique +# 56| v4_6(void) = ConditionalBranch : r4_5 +#-----| False -> Block 6 +#-----| True -> Block 5 + +# 56| Block 5 +# 56| r5_0(glval) = VariableAddress[ptr] : +# 56| valnum = r0_6 +# 56| r5_1(char *) = Load : r5_0, m3_0 +# 56| valnum = unique +# 56| r5_2(int) = Constant[1] : +# 56| valnum = unique +# 56| r5_3(char *) = PointerAdd[1] : r5_1, r5_2 +# 56| valnum = r5_3 +# 56| m5_4(char *) = Store : r5_0, r5_3 +# 56| valnum = r5_3 +#-----| Goto -> Block 3 + +# 59| Block 6 +# 59| r6_0(glval) = VariableAddress[ptr] : +# 59| valnum = r0_6 +# 59| r6_1(char *) = Load : r6_0, m3_0 +# 59| valnum = unique +# 59| r6_2(char) = Load : r6_1, mu0_1 +# 59| valnum = unique +# 59| r6_3(int) = Convert : r6_2 +# 59| valnum = unique +# 59| r6_4(int) = Constant[0] : +# 59| valnum = r1_5 +# 59| r6_5(bool) = CompareEQ : r6_3, r6_4 +# 59| valnum = unique +# 59| v6_6(void) = ConditionalBranch : r6_5 +#-----| False -> Block 8 +#-----| True -> Block 7 + +# 60| Block 7 +# 60| v7_0(void) = NoOp : +#-----| Goto -> Block 9 + +# 62| Block 8 +# 62| r8_0(glval) = VariableAddress[result] : +# 62| valnum = r0_8 +# 62| r8_1(unsigned int) = Load : r8_0, m1_0 +# 62| valnum = unique +# 62| r8_2(unsigned int) = Constant[1] : +# 62| valnum = unique +# 62| r8_3(unsigned int) = Add : r8_1, r8_2 +# 62| valnum = r8_3 +# 62| m8_4(unsigned int) = Store : r8_0, r8_3 +# 62| valnum = r8_3 +#-----| Goto -> Block 1 + +# 63| Block 9 +# 63| v9_0(void) = NoOp : +# 65| r9_1(glval) = VariableAddress[#return] : +# 65| valnum = r9_1 +# 65| r9_2(glval) = VariableAddress[result] : +# 65| valnum = r0_8 +# 65| r9_3(unsigned int) = Load : r9_2, m1_0 +# 65| valnum = r9_3 +# 65| m9_4(unsigned int) = Store : r9_1, r9_3 +# 65| valnum = r9_3 +# 49| r9_5(glval) = VariableAddress[#return] : +# 49| valnum = r9_1 +# 49| v9_6(void) = ReturnValue : r9_5, m9_4 +# 49| v9_7(void) = UnmodeledUse : mu* +# 49| v9_8(void) = ExitFunction : + +# 75| test04(two_values *) -> void +# 75| Block 0 +# 75| v0_0(void) = EnterFunction : +# 75| mu0_1(unknown) = UnmodeledDefinition : +# 75| valnum = unique +# 75| r0_2(glval) = VariableAddress[vals] : +# 75| valnum = r0_2 +# 75| m0_3(two_values *) = InitializeParameter[vals] : r0_2 +# 75| valnum = m0_3 +# 77| r0_4(glval) = VariableAddress[v] : +# 77| valnum = r0_4 +# 77| r0_5(glval) = FunctionAddress[getAValue] : +# 77| valnum = unique +# 77| r0_6(int) = Call : r0_5 +# 77| valnum = unique +# 77| r0_7(signed short) = Convert : r0_6 +# 77| valnum = r0_7 +# 77| m0_8(signed short) = Store : r0_4, r0_7 +# 77| valnum = r0_7 +# 79| r0_9(glval) = VariableAddress[v] : +# 79| valnum = r0_4 +# 79| r0_10(signed short) = Load : r0_9, m0_8 +# 79| valnum = r0_7 +# 79| r0_11(int) = Convert : r0_10 +# 79| valnum = unique +# 79| r0_12(glval) = VariableAddress[vals] : +# 79| valnum = r0_2 +# 79| r0_13(two_values *) = Load : r0_12, m0_3 +# 79| valnum = m0_3 +# 79| r0_14(glval) = FieldAddress[val1] : r0_13 +# 79| valnum = unique +# 79| r0_15(signed short) = Load : r0_14, mu0_1 +# 79| valnum = unique +# 79| r0_16(int) = Convert : r0_15 +# 79| valnum = unique +# 79| r0_17(glval) = VariableAddress[vals] : +# 79| valnum = r0_2 +# 79| r0_18(two_values *) = Load : r0_17, m0_3 +# 79| valnum = m0_3 +# 79| r0_19(glval) = FieldAddress[val2] : r0_18 +# 79| valnum = unique +# 79| r0_20(signed short) = Load : r0_19, mu0_1 +# 79| valnum = unique +# 79| r0_21(int) = Convert : r0_20 +# 79| valnum = unique +# 79| r0_22(int) = Add : r0_16, r0_21 +# 79| valnum = unique +# 79| r0_23(bool) = CompareLT : r0_11, r0_22 +# 79| valnum = unique +# 79| v0_24(void) = ConditionalBranch : r0_23 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 80| Block 1 +# 80| r1_0(glval) = FunctionAddress[getAValue] : +# 80| valnum = unique +# 80| r1_1(int) = Call : r1_0 +# 80| valnum = unique +# 80| r1_2(signed short) = Convert : r1_1 +# 80| valnum = r1_2 +# 80| r1_3(glval) = VariableAddress[v] : +# 80| valnum = r0_4 +# 80| m1_4(signed short) = Store : r1_3, r1_2 +# 80| valnum = r1_2 +#-----| Goto -> Block 2 + +# 82| Block 2 +# 82| v2_0(void) = NoOp : +# 75| v2_1(void) = ReturnVoid : +# 75| v2_2(void) = UnmodeledUse : mu* +# 75| v2_3(void) = ExitFunction : + +# 84| test05(int, int, void *) -> void +# 84| Block 0 +# 84| v0_0(void) = EnterFunction : +# 84| mu0_1(unknown) = UnmodeledDefinition : +# 84| valnum = unique +# 84| r0_2(glval) = VariableAddress[x] : +# 84| valnum = r0_2 +# 84| m0_3(int) = InitializeParameter[x] : r0_2 +# 84| valnum = m0_3 +# 84| r0_4(glval) = VariableAddress[y] : +# 84| valnum = r0_4 +# 84| m0_5(int) = InitializeParameter[y] : r0_4 +# 84| valnum = m0_5 +# 84| r0_6(glval) = VariableAddress[p] : +# 84| valnum = r0_6 +# 84| m0_7(void *) = InitializeParameter[p] : r0_6 +# 84| valnum = m0_7 +# 86| r0_8(glval) = VariableAddress[v] : +# 86| valnum = r0_8 +# 86| m0_9(int) = Uninitialized : r0_8 +# 86| valnum = unique +# 88| r0_10(glval) = VariableAddress[p] : +# 88| valnum = r0_6 +# 88| r0_11(void *) = Load : r0_10, m0_7 +# 88| valnum = m0_7 +# 88| r0_12(void *) = Constant[0] : +# 88| valnum = unique +# 88| r0_13(bool) = CompareNE : r0_11, r0_12 +# 88| valnum = unique +# 88| v0_14(void) = ConditionalBranch : r0_13 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 88| Block 1 +# 88| r1_0(glval) = VariableAddress[x] : +# 88| valnum = r0_2 +# 88| r1_1(int) = Load : r1_0, m0_3 +# 88| valnum = m0_3 +# 88| r1_2(glval) = VariableAddress[#temp88:7] : +# 88| valnum = r1_2 +# 88| m1_3(int) = Store : r1_2, r1_1 +# 88| valnum = m0_3 +#-----| Goto -> Block 3 + +# 88| Block 2 +# 88| r2_0(glval) = VariableAddress[y] : +# 88| valnum = r0_4 +# 88| r2_1(int) = Load : r2_0, m0_5 +# 88| valnum = m0_5 +# 88| r2_2(glval) = VariableAddress[#temp88:7] : +# 88| valnum = r1_2 +# 88| m2_3(int) = Store : r2_2, r2_1 +# 88| valnum = m0_5 +#-----| Goto -> Block 3 + +# 88| Block 3 +# 88| m3_0(int) = Phi : from 1:m1_3, from 2:m2_3 +# 88| valnum = unique +# 88| r3_1(glval) = VariableAddress[#temp88:7] : +# 88| valnum = r1_2 +# 88| r3_2(int) = Load : r3_1, m3_0 +# 88| valnum = r3_2 +# 88| r3_3(glval) = VariableAddress[v] : +# 88| valnum = r0_8 +# 88| m3_4(int) = Store : r3_3, r3_2 +# 88| valnum = r3_2 +# 89| v3_5(void) = NoOp : +# 84| v3_6(void) = ReturnVoid : +# 84| v3_7(void) = UnmodeledUse : mu* +# 84| v3_8(void) = ExitFunction : + +# 91| regression_test00() -> int +# 91| Block 0 +# 91| v0_0(void) = EnterFunction : +# 91| mu0_1(unknown) = UnmodeledDefinition : +# 91| valnum = unique +# 92| r0_2(glval) = VariableAddress[x] : +# 92| valnum = r0_2 +# 92| r0_3(int) = Constant[10] : +# 92| valnum = r0_3 +# 92| r0_4(glval) = VariableAddress[x] : +# 92| valnum = r0_2 +# 92| m0_5(int) = Store : r0_4, r0_3 +# 92| valnum = r0_3 +# 92| m0_6(int) = Store : r0_2, r0_3 +# 92| valnum = r0_3 +# 93| r0_7(glval) = VariableAddress[#return] : +# 93| valnum = r0_7 +# 93| r0_8(glval) = VariableAddress[x] : +# 93| valnum = r0_2 +# 93| r0_9(int) = Load : r0_8, m0_6 +# 93| valnum = r0_3 +# 93| m0_10(int) = Store : r0_7, r0_9 +# 93| valnum = r0_3 +# 91| r0_11(glval) = VariableAddress[#return] : +# 91| valnum = r0_7 +# 91| v0_12(void) = ReturnValue : r0_11, m0_10 +# 91| v0_13(void) = UnmodeledUse : mu* +# 91| v0_14(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql new file mode 100644 index 00000000000..2b9521af1ae --- /dev/null +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql @@ -0,0 +1,6 @@ +/** + * @kind graph + */ +import semmle.code.cpp.ir.PrintIR +import semmle.code.cpp.ir.IR +import semmle.code.cpp.ir.implementation.aliased_ssa.internal.gvn.ValueNumber From 43f0289f0f84ff49ac0f11987099b23b40f9de6c Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 18 Sep 2018 11:28:09 -0700 Subject: [PATCH 64/73] C++: Remove Phi instructions from previous IR generations It turns out that when building aliased SSA IR, we were still keeping around the Phi instructions from unaliased SSA IR. These leftover instructions didn't show up in dumps because they were not assigned to a block. However, when dumping additional instruction properties, they would show up as a top-level node in the dump, without a label. --- .../cpp/ir/implementation/aliased_ssa/Instruction.qll | 8 ++++++++ .../aliased_ssa/internal/SSAConstruction.qll | 4 +++- .../code/cpp/ir/implementation/raw/Instruction.qll | 8 ++++++++ .../cpp/ir/implementation/unaliased_ssa/Instruction.qll | 8 ++++++++ .../unaliased_ssa/internal/SSAConstruction.qll | 4 +++- .../test/library-tests/ir/ir/aliased_ssa_sanity.expected | 1 + cpp/ql/test/library-tests/ir/ir/raw_sanity.expected | 1 + .../library-tests/ir/ir/unaliased_ssa_sanity.expected | 1 + .../valuenumbering/GlobalValueNumbering/ir_gvn.expected | 9 --------- 9 files changed, 33 insertions(+), 11 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 a7046ce467d..e63fd979def 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 @@ -107,6 +107,14 @@ module InstructionSanity { operand = op.getOperand(tag) and operand.getFunctionIR() != op.getFunctionIR() } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index 4a992ffb4ce..f6e2034296d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -30,7 +30,9 @@ cached private module Cached { } cached newtype TInstructionTag = - WrappedInstructionTag(OldIR::Instruction oldInstruction) or + WrappedInstructionTag(OldIR::Instruction oldInstruction) { + not oldInstruction instanceof OldIR::PhiInstruction + } or PhiTag(Alias::VirtualVariable vvar, OldIR::IRBlock block) { hasPhiNode(vvar, block) } 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 a7046ce467d..e63fd979def 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 @@ -107,6 +107,14 @@ module InstructionSanity { operand = op.getOperand(tag) and operand.getFunctionIR() != op.getFunctionIR() } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } } /** 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 a7046ce467d..e63fd979def 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 @@ -107,6 +107,14 @@ module InstructionSanity { operand = op.getOperand(tag) and operand.getFunctionIR() != op.getFunctionIR() } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 4a992ffb4ce..f6e2034296d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -30,7 +30,9 @@ cached private module Cached { } cached newtype TInstructionTag = - WrappedInstructionTag(OldIR::Instruction oldInstruction) or + WrappedInstructionTag(OldIR::Instruction oldInstruction) { + not oldInstruction instanceof OldIR::PhiInstruction + } or PhiTag(Alias::VirtualVariable vvar, OldIR::IRBlock block) { hasPhiNode(vvar, block) } diff --git a/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected b/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected index 001551870fa..95989fc8809 100644 --- a/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected @@ -5,3 +5,4 @@ missingPhiOperand instructionWithoutSuccessor unnecessaryPhiInstruction operandAcrossFunctions +instructionWithoutUniqueBlock diff --git a/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected b/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected index 001551870fa..95989fc8809 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected @@ -5,3 +5,4 @@ missingPhiOperand instructionWithoutSuccessor unnecessaryPhiInstruction operandAcrossFunctions +instructionWithoutUniqueBlock diff --git a/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected b/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected index 001551870fa..95989fc8809 100644 --- a/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected @@ -5,3 +5,4 @@ missingPhiOperand instructionWithoutSuccessor unnecessaryPhiInstruction operandAcrossFunctions +instructionWithoutUniqueBlock diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected index 479df8570b8..35b507a61f1 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected @@ -67,15 +67,6 @@ test.cpp: # 1| v0_33(void) = UnmodeledUse : mu* # 1| v0_34(void) = ExitFunction : -# 53| 1644 -# 53| valnum = unique - -# 56| 1646 -# 56| valnum = unique - -# 88| 1755 -# 88| valnum = unique - # 12| test01(int, int) -> int # 12| Block 0 # 12| v0_0(void) = EnterFunction : From 2cedc817744f3d061116cc0dd89a61cf4fc47ebb Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Mon, 17 Sep 2018 12:18:05 +0200 Subject: [PATCH 65/73] JS: polish js/enabling-electron-renderer-node-integration meta info --- change-notes/1.19/analysis-javascript.md | 1 + javascript/config/suites/javascript/security | 1 + .../Electron/EnablingNodeIntegration.qhelp | 51 +++++++++++-------- .../src/Electron/EnablingNodeIntegration.ql | 4 +- .../examples/DefaultNodeIntegration.js | 2 - .../examples/EnablingNodeIntegration.js | 33 +++++------- 6 files changed, 49 insertions(+), 43 deletions(-) delete mode 100644 javascript/ql/src/Electron/examples/DefaultNodeIntegration.js diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index 4d23f695d9e..4415af95d30 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -12,6 +12,7 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Enabling Node.js integration for Electron web content renderers (`js/enabling-electron-renderer-node-integration`) | security, frameworks/electron, external/cwe/cwe-094 | Highlights Electron web content renderer preferences with Node.js integration enabled, indicating a violation of [CWE-94](https://cwe.mitre.org/data/definitions/94.html). Results are not shown on LGTM by default. | | Stored cross-site scripting (`js/stored-xss`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights uncontrolled stored values flowing into HTML content, indicating a violation of [CWE-079](https://cwe.mitre.org/data/definitions/79.html). Results shown on LGTM by default. | ## Changes to existing queries diff --git a/javascript/config/suites/javascript/security b/javascript/config/suites/javascript/security index e55f89019f6..2f0fd0dabc0 100644 --- a/javascript/config/suites/javascript/security +++ b/javascript/config/suites/javascript/security @@ -1,4 +1,5 @@ + semmlecode-javascript-queries/DOM/TargetBlank.ql: /Security/CWE/CWE-200 ++ semmlecode-javascript-queries/Electron/EnablingNodeIntegration.ql: /Security/CWE/CWE-094 + semmlecode-javascript-queries/Security/CWE-022/TaintedPath.ql: /Security/CWE/CWE-022 + semmlecode-javascript-queries/Security/CWE-078/CommandInjection.ql: /Security/CWE/CWE-078 + semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079 diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp index c6a9af938d7..34efea3b347 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.qhelp @@ -5,39 +5,48 @@

    - Enabling Node.js integration in web content renderers (BrowserWindow, BrowserView and webview) could result in - remote native code execution attacks when rendering malicious JavaScript code from untrusted remote web site or - code that is injected via a cross site scripting vulnerability into a trusted remote web site. Note that - the nodeIntegration property is enabled by default in Electron and needs to be set to false explicitly. -

    + + Enabling Node.js integration in Electron web content renderers + (BrowserWindow, BrowserView and + webview) can result in remote native code execution + attacks. + + The attack is realized when the renderer uses content from an + untrusted remote web site or a trusted site with a cross site + scripting vulnerability. + +

    - Node.js integration should be disabled when loading remote web sites. If not possible, always set nodeIntegration property - to 'false' before loading remote web sites and only enable it for whitelisted sites. + + Node.js integration should be disabled when loading remote web + sites. Always set nodeIntegration preference + to false before loading remote web sites, and only enable + it for whitelisted sites. + +

    + +

    + + Note that the nodeIntegration property is enabled + by default in Electron and needs to be set to false + explicitly. +

    -

    - The following example shows insecure use of BrowserWindow with regards to nodeIntegration - property: -

    -

    - This is problematic, because default value of nodeIntegration is 'true'. -

    - -
    - - -

    - The following example shows insecure and secure uses of BrowserWindow and BrowserView when - loading untrusted web sites: + The following examples shows insecure and secure uses of + BrowserWindow and BrowserView when loading + remote web sites: +

    +
    diff --git a/javascript/ql/src/Electron/EnablingNodeIntegration.ql b/javascript/ql/src/Electron/EnablingNodeIntegration.ql index f38d854c1dc..d8e764012a2 100644 --- a/javascript/ql/src/Electron/EnablingNodeIntegration.ql +++ b/javascript/ql/src/Electron/EnablingNodeIntegration.ql @@ -1,11 +1,13 @@ /** - * @name Enabling `nodeIntegration` or `nodeIntegrationInWorker` for Electron web content + * @name Enabling Node.js integration for Electron web content renderers * @description Enabling `nodeIntegration` or `nodeIntegrationInWorker` can expose the application to remote code execution. * @kind problem * @problem.severity warning + * @precision low * @id js/enabling-electron-renderer-node-integration * @tags security * frameworks/electron + * external/cwe/cwe-094 */ import javascript diff --git a/javascript/ql/src/Electron/examples/DefaultNodeIntegration.js b/javascript/ql/src/Electron/examples/DefaultNodeIntegration.js deleted file mode 100644 index 781d31f59f6..00000000000 --- a/javascript/ql/src/Electron/examples/DefaultNodeIntegration.js +++ /dev/null @@ -1,2 +0,0 @@ -const win = new BrowserWindow(); -win.loadURL("https://untrusted-site.com"); \ No newline at end of file diff --git a/javascript/ql/src/Electron/examples/EnablingNodeIntegration.js b/javascript/ql/src/Electron/examples/EnablingNodeIntegration.js index 7a6312a807b..87510f06265 100644 --- a/javascript/ql/src/Electron/examples/EnablingNodeIntegration.js +++ b/javascript/ql/src/Electron/examples/EnablingNodeIntegration.js @@ -1,26 +1,21 @@ -//BAD -win_1 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: true}}); -win_1.loadURL("https://untrusted-site.com"); +//BAD: `nodeIntegration` enabled by default +var win_1 = new BrowserWindow(); +win_1.loadURL(remote_site); -//GOOD -win_2 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: false}}); -win_2.loadURL("https://untrusted-site.com"); +//BAD: `nodeIntegration` enabled +var win_2 = new BrowserWindow({webPreferences: {nodeIntegration: true}}); +win_2.loadURL(remote_site); -//BAD -win_3 = new BrowserWindow({ - webPreferences: { - nodeIntegrationInWorker: true - } -}); +//GOOD: `nodeIntegration` disabled +let win_3 = new BrowserWindow({webPreferences: {nodeIntegration: false}}); +win_3.loadURL(remote_site); -//BAD BrowserView -win_4 = new BrowserWindow({width: 800, height: 600, webPreferences: {nodeIntegration: false}}) -view = new BrowserView({ +//BAD: `nodeIntegration` enabled in the view +var win_4 = new BrowserWindow({webPreferences: {nodeIntegration: false}}) +var view_4 = new BrowserView({ webPreferences: { nodeIntegration: true } }); -win.setBrowserView(view); -view.setBounds({ x: 0, y: 0, width: 300, height: 300 }); -view.webContents.loadURL('https://untrusted-site.com'); - +win_4.setBrowserView(view_4); +view_4.webContents.loadURL(remote_site); From 1d793c0a7b853ddb0ae64622cfcee310ccd7865c Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 19 Sep 2018 14:33:23 +0100 Subject: [PATCH 66/73] JavaScript: fix expected output --- .../ServerSideUrlRedirect/ServerSideUrlRedirect.expected | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected index 2bf3cc7dd5b..fef8354768b 100644 --- a/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected +++ b/javascript/ql/test/query-tests/Security/CWE-601/ServerSideUrlRedirect/ServerSideUrlRedirect.expected @@ -6,7 +6,7 @@ | express.js:78:16:78:43 | `${req. ... )}/foo` | Untrusted URL redirection due to $@. | express.js:78:19:78:37 | req.param("target") | user-provided value | | express.js:94:18:94:23 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value | | express.js:101:16:101:21 | target | Untrusted URL redirection due to $@. | express.js:87:16:87:34 | req.param("target") | user-provided value | -| express.js:119:16:119:72 | [req.qu ... oin('') | Untrusted URL redirection due to $@. | express.js:119:17:119:30 | req.query.page | user-provided value | +| express.js:122:16:122:72 | [req.qu ... oin('') | Untrusted URL redirection due to $@. | express.js:122:17:122:30 | req.query.page | user-provided value | | node.js:7:34:7:39 | target | Untrusted URL redirection due to $@. | node.js:6:26:6:32 | req.url | user-provided value | | node.js:15:34:15:45 | '/' + target | Untrusted URL redirection due to $@. | node.js:11:26:11:32 | req.url | user-provided value | | node.js:32:34:32:55 | target ... =" + me | Untrusted URL redirection due to $@. | node.js:29:26:29:32 | req.url | user-provided value | From bd156757d3dcb4069657b6c78aa17f441cd51bfd Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 19 Sep 2018 14:26:17 -0700 Subject: [PATCH 67/73] C++: Remove accidental add of IR.md --- cpp/ql/src/semmle/code/cpp/ir/IR.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 cpp/ql/src/semmle/code/cpp/ir/IR.md diff --git a/cpp/ql/src/semmle/code/cpp/ir/IR.md b/cpp/ql/src/semmle/code/cpp/ir/IR.md deleted file mode 100644 index e40e04d4a3d..00000000000 --- a/cpp/ql/src/semmle/code/cpp/ir/IR.md +++ /dev/null @@ -1,7 +0,0 @@ -# The C/C++ Intermediate Representation - -## Introduction - -The Intermediate Representation (IR) library provides a representation of the semantics of the program, independent of the syntax used to express those semantics. The IR is similar in design to representations used in optimizing compilers, such as LLVM IR. - -## Memory Access From b12c739915f7917ca1b458428ac598bf6c8e85f0 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 19 Sep 2018 11:34:37 -0700 Subject: [PATCH 68/73] JavaScript: Normalize line endings of .js and .html files Added .gitattributes files for the two directories where we intentionally have line endings other than LF --- .editorconfig | 2 +- .gitattributes | 2 + .../test/library-tests/Lines/.gitattributes | 2 + .../AlertSuppression/.gitattributes | 3 + .../AlertSuppression/tstWindows.html | 14 ++--- .../AlertSuppression/tstWindows.js | 56 +++++++++---------- 6 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 javascript/ql/test/library-tests/Lines/.gitattributes create mode 100644 javascript/ql/test/query-tests/AlertSuppression/.gitattributes diff --git a/.editorconfig b/.editorconfig index 476ae898a0f..eead7fb34a9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,2 @@ -[*.{ql,qll,qlref,dbscheme,qhelp}] +[*.{ql,qll,qlref,dbscheme,qhelp,js,html}] end_of_line = lf diff --git a/.gitattributes b/.gitattributes index a6c5703f96b..500108dbffa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,3 +15,5 @@ *.qlref eol=lf *.dbscheme eol=lf *.qhelp eol=lf +*.html eol=lf +*.js eol=lf diff --git a/javascript/ql/test/library-tests/Lines/.gitattributes b/javascript/ql/test/library-tests/Lines/.gitattributes new file mode 100644 index 00000000000..4d67c9960a4 --- /dev/null +++ b/javascript/ql/test/library-tests/Lines/.gitattributes @@ -0,0 +1,2 @@ +# This file intentionally contains a mix of different line endings +tst1.js -text diff --git a/javascript/ql/test/query-tests/AlertSuppression/.gitattributes b/javascript/ql/test/query-tests/AlertSuppression/.gitattributes new file mode 100644 index 00000000000..c2f08d06d7e --- /dev/null +++ b/javascript/ql/test/query-tests/AlertSuppression/.gitattributes @@ -0,0 +1,3 @@ +# These files intentionally contain CRLF line endings. +tstWindows.html eol=crlf +tstWindows.js eol=crlf diff --git a/javascript/ql/test/query-tests/AlertSuppression/tstWindows.html b/javascript/ql/test/query-tests/AlertSuppression/tstWindows.html index 94ce5a077ee..ec556d0b278 100644 --- a/javascript/ql/test/query-tests/AlertSuppression/tstWindows.html +++ b/javascript/ql/test/query-tests/AlertSuppression/tstWindows.html @@ -1,7 +1,7 @@ - - Title - -
    -
    - - + + Title + +
    +
    + + diff --git a/javascript/ql/test/query-tests/AlertSuppression/tstWindows.js b/javascript/ql/test/query-tests/AlertSuppression/tstWindows.js index 80ad004b11b..1bbb7d4c5e8 100644 --- a/javascript/ql/test/query-tests/AlertSuppression/tstWindows.js +++ b/javascript/ql/test/query-tests/AlertSuppression/tstWindows.js @@ -1,28 +1,28 @@ -debugger; // lgtm -// lgtm[js/debugger-statement] -// lgtm[js/debugger-statement, js/invocation-of-non-function] -// lgtm[@tag:nullness] -// lgtm[@tag:nullness,js/debugger-statement] -// lgtm[@expires:2017-06-11] -// lgtm[js/invocation-of-non-function] because I know better than lgtm -// lgtm: blah blah -// lgtm blah blah #falsepositive -//lgtm [js/invocation-of-non-function] -/* lgtm */ -// lgtm[] -// lgtmfoo -//lgtm -// lgtm -// lgtm [js/debugger-statement] -// foolgtm[js/debugger-statement] -// foolgtm -// foo; lgtm -// foo; lgtm[js/debugger-statement] -// foo lgtm -// foo lgtm[js/debugger-statement] -// foo lgtm bar -// foo lgtm[js/debugger-statement] bar -// LGTM! -// LGTM[js/debugger-statement] -// lgtm[js/debugger-statement] and lgtm[js/invocation-of-non-function] -// lgtm[js/debugger-statement]; lgtm +debugger; // lgtm +// lgtm[js/debugger-statement] +// lgtm[js/debugger-statement, js/invocation-of-non-function] +// lgtm[@tag:nullness] +// lgtm[@tag:nullness,js/debugger-statement] +// lgtm[@expires:2017-06-11] +// lgtm[js/invocation-of-non-function] because I know better than lgtm +// lgtm: blah blah +// lgtm blah blah #falsepositive +//lgtm [js/invocation-of-non-function] +/* lgtm */ +// lgtm[] +// lgtmfoo +//lgtm +// lgtm +// lgtm [js/debugger-statement] +// foolgtm[js/debugger-statement] +// foolgtm +// foo; lgtm +// foo; lgtm[js/debugger-statement] +// foo lgtm +// foo lgtm[js/debugger-statement] +// foo lgtm bar +// foo lgtm[js/debugger-statement] bar +// LGTM! +// LGTM[js/debugger-statement] +// lgtm[js/debugger-statement] and lgtm[js/invocation-of-non-function] +// lgtm[js/debugger-statement]; lgtm From 2b9f42b30875e12af7e2d2f403272e5ece62e7d1 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 19 Sep 2018 12:02:18 -0700 Subject: [PATCH 69/73] JavaScript: Force LF for .json and .yml --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 500108dbffa..69ff97dca0c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,3 +17,5 @@ *.qhelp eol=lf *.html eol=lf *.js eol=lf +*.json eol=lf +*.yml eol=lf From 524c67c3fbf152f4d550c6e46cdd5737dddcdf29 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 19 Sep 2018 13:07:02 -0700 Subject: [PATCH 70/73] JavaScript: Normalize .ts line endings to LF --- .editorconfig | 2 +- .gitattributes | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index eead7fb34a9..c6cc3fea357 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,2 @@ -[*.{ql,qll,qlref,dbscheme,qhelp,js,html}] +[*.{ql,qll,qlref,dbscheme,qhelp,html,js,ts,json,yml}] end_of_line = lf diff --git a/.gitattributes b/.gitattributes index 69ff97dca0c..80652c576f8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,5 +17,6 @@ *.qhelp eol=lf *.html eol=lf *.js eol=lf +*.ts eol=lf *.json eol=lf *.yml eol=lf From e06969ddb4605ad288b51bea8e87232b2029adb7 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 19 Sep 2018 20:18:57 -0700 Subject: [PATCH 71/73] JavaScript: Normalize .mjs files to LF --- .editorconfig | 2 +- .gitattributes | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index c6cc3fea357..decc2081a24 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,2 @@ -[*.{ql,qll,qlref,dbscheme,qhelp,html,js,ts,json,yml}] +[*.{ql,qll,qlref,dbscheme,qhelp,html,js,mjs,ts,json,yml}] end_of_line = lf diff --git a/.gitattributes b/.gitattributes index 80652c576f8..ec825a38b57 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,6 +17,7 @@ *.qhelp eol=lf *.html eol=lf *.js eol=lf +*.mjs eol=lf *.ts eol=lf *.json eol=lf *.yml eol=lf From 27cee9bd80a10066a6a807b3ec07f3e895d7a67b Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Thu, 20 Sep 2018 08:00:38 -0700 Subject: [PATCH 72/73] C++: Handle inheritance conversions in IR GVN --- .../aliased_ssa/internal/gvn/ValueNumber.qll | 26 +++++--- .../raw/internal/gvn/ValueNumber.qll | 26 +++++--- .../internal/gvn/ValueNumber.qll | 26 +++++--- .../GlobalValueNumbering.expected | 3 + .../GlobalValueNumbering/ir_gvn.expected | 59 +++++++++++++++++++ .../GlobalValueNumbering/test.cpp | 16 +++++ 6 files changed, 132 insertions(+), 24 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll index e682c417ba2..1f91e6cac1a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll @@ -45,6 +45,10 @@ newtype TValueNumber = TUnaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber operand) { unaryValueNumber(_, funcIR, opcode, type, operand) } or + TInheritanceConversionValueNumber(FunctionIR funcIR, Opcode opcode, Class baseClass, + Class derivedClass, ValueNumber operand) { + inheritanceConversionValueNumber(_, funcIR, opcode, baseClass, derivedClass, operand) + } or TUniqueValueNumber(FunctionIR funcIR, Instruction instr) { uniqueValueNumber(instr, funcIR) } @@ -81,13 +85,6 @@ class ValueNumber extends TValueNumber { } } -class BinaryValueNumber extends ValueNumber, TBinaryValueNumber {} -class UnaryValueNumber extends ValueNumber, TUnaryValueNumber {} -class ConstantValueNumber extends ValueNumber, TConstantValueNumber {} -class FieldAddressValueNumber extends ValueNumber, TFieldAddressValueNumber {} -class PointerArithmeticValueNumber extends ValueNumber, TPointerArithmeticValueNumber {} -class UniqueValueNumber extends ValueNumber, TUniqueValueNumber {} - /** * A `CopyInstruction` whose source operand's value is congruent to the definition of that source * operand. @@ -187,12 +184,20 @@ private predicate unaryValueNumber(UnaryInstruction instr, FunctionIR funcIR, Op Type type, ValueNumber operand) { instr.getFunctionIR() = funcIR and (not instr instanceof InheritanceConversionInstruction) and - (not instr instanceof FieldAddressInstruction) and instr.getOpcode() = opcode and instr.getResultType() = type and valueNumber(instr.getOperand()) = operand } +private predicate inheritanceConversionValueNumber(InheritanceConversionInstruction instr, + FunctionIR funcIR, Opcode opcode, Class baseClass, Class derivedClass, ValueNumber operand) { + instr.getFunctionIR() = funcIR and + instr.getOpcode() = opcode and + instr.getBaseClass() = baseClass and + instr.getDerivedClass() = derivedClass and + valueNumber(instr.getOperand()) = operand +} + /** * Holds if `instr` should be assigned a unique value number because this library does not know how * to determine if two instances of that instruction are equivalent. @@ -250,6 +255,11 @@ private ValueNumber nonUniqueValueNumber(Instruction instr) { unaryValueNumber(instr, funcIR, opcode, type, operand) and result = TUnaryValueNumber(funcIR, opcode, type, operand) ) or + exists(Opcode opcode, Class baseClass, Class derivedClass, ValueNumber operand | + inheritanceConversionValueNumber(instr, funcIR, opcode, baseClass, derivedClass, + operand) and + result = TInheritanceConversionValueNumber(funcIR, opcode, baseClass, derivedClass, operand) + ) or exists(Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, ValueNumber rightOperand | pointerArithmeticValueNumber(instr, funcIR, opcode, type, elementSize, leftOperand, diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll index e682c417ba2..1f91e6cac1a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll @@ -45,6 +45,10 @@ newtype TValueNumber = TUnaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber operand) { unaryValueNumber(_, funcIR, opcode, type, operand) } or + TInheritanceConversionValueNumber(FunctionIR funcIR, Opcode opcode, Class baseClass, + Class derivedClass, ValueNumber operand) { + inheritanceConversionValueNumber(_, funcIR, opcode, baseClass, derivedClass, operand) + } or TUniqueValueNumber(FunctionIR funcIR, Instruction instr) { uniqueValueNumber(instr, funcIR) } @@ -81,13 +85,6 @@ class ValueNumber extends TValueNumber { } } -class BinaryValueNumber extends ValueNumber, TBinaryValueNumber {} -class UnaryValueNumber extends ValueNumber, TUnaryValueNumber {} -class ConstantValueNumber extends ValueNumber, TConstantValueNumber {} -class FieldAddressValueNumber extends ValueNumber, TFieldAddressValueNumber {} -class PointerArithmeticValueNumber extends ValueNumber, TPointerArithmeticValueNumber {} -class UniqueValueNumber extends ValueNumber, TUniqueValueNumber {} - /** * A `CopyInstruction` whose source operand's value is congruent to the definition of that source * operand. @@ -187,12 +184,20 @@ private predicate unaryValueNumber(UnaryInstruction instr, FunctionIR funcIR, Op Type type, ValueNumber operand) { instr.getFunctionIR() = funcIR and (not instr instanceof InheritanceConversionInstruction) and - (not instr instanceof FieldAddressInstruction) and instr.getOpcode() = opcode and instr.getResultType() = type and valueNumber(instr.getOperand()) = operand } +private predicate inheritanceConversionValueNumber(InheritanceConversionInstruction instr, + FunctionIR funcIR, Opcode opcode, Class baseClass, Class derivedClass, ValueNumber operand) { + instr.getFunctionIR() = funcIR and + instr.getOpcode() = opcode and + instr.getBaseClass() = baseClass and + instr.getDerivedClass() = derivedClass and + valueNumber(instr.getOperand()) = operand +} + /** * Holds if `instr` should be assigned a unique value number because this library does not know how * to determine if two instances of that instruction are equivalent. @@ -250,6 +255,11 @@ private ValueNumber nonUniqueValueNumber(Instruction instr) { unaryValueNumber(instr, funcIR, opcode, type, operand) and result = TUnaryValueNumber(funcIR, opcode, type, operand) ) or + exists(Opcode opcode, Class baseClass, Class derivedClass, ValueNumber operand | + inheritanceConversionValueNumber(instr, funcIR, opcode, baseClass, derivedClass, + operand) and + result = TInheritanceConversionValueNumber(funcIR, opcode, baseClass, derivedClass, operand) + ) or exists(Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, ValueNumber rightOperand | pointerArithmeticValueNumber(instr, funcIR, opcode, type, elementSize, leftOperand, diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll index e682c417ba2..1f91e6cac1a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll @@ -45,6 +45,10 @@ newtype TValueNumber = TUnaryValueNumber(FunctionIR funcIR, Opcode opcode, Type type, ValueNumber operand) { unaryValueNumber(_, funcIR, opcode, type, operand) } or + TInheritanceConversionValueNumber(FunctionIR funcIR, Opcode opcode, Class baseClass, + Class derivedClass, ValueNumber operand) { + inheritanceConversionValueNumber(_, funcIR, opcode, baseClass, derivedClass, operand) + } or TUniqueValueNumber(FunctionIR funcIR, Instruction instr) { uniqueValueNumber(instr, funcIR) } @@ -81,13 +85,6 @@ class ValueNumber extends TValueNumber { } } -class BinaryValueNumber extends ValueNumber, TBinaryValueNumber {} -class UnaryValueNumber extends ValueNumber, TUnaryValueNumber {} -class ConstantValueNumber extends ValueNumber, TConstantValueNumber {} -class FieldAddressValueNumber extends ValueNumber, TFieldAddressValueNumber {} -class PointerArithmeticValueNumber extends ValueNumber, TPointerArithmeticValueNumber {} -class UniqueValueNumber extends ValueNumber, TUniqueValueNumber {} - /** * A `CopyInstruction` whose source operand's value is congruent to the definition of that source * operand. @@ -187,12 +184,20 @@ private predicate unaryValueNumber(UnaryInstruction instr, FunctionIR funcIR, Op Type type, ValueNumber operand) { instr.getFunctionIR() = funcIR and (not instr instanceof InheritanceConversionInstruction) and - (not instr instanceof FieldAddressInstruction) and instr.getOpcode() = opcode and instr.getResultType() = type and valueNumber(instr.getOperand()) = operand } +private predicate inheritanceConversionValueNumber(InheritanceConversionInstruction instr, + FunctionIR funcIR, Opcode opcode, Class baseClass, Class derivedClass, ValueNumber operand) { + instr.getFunctionIR() = funcIR and + instr.getOpcode() = opcode and + instr.getBaseClass() = baseClass and + instr.getDerivedClass() = derivedClass and + valueNumber(instr.getOperand()) = operand +} + /** * Holds if `instr` should be assigned a unique value number because this library does not know how * to determine if two instances of that instruction are equivalent. @@ -250,6 +255,11 @@ private ValueNumber nonUniqueValueNumber(Instruction instr) { unaryValueNumber(instr, funcIR, opcode, type, operand) and result = TUnaryValueNumber(funcIR, opcode, type, operand) ) or + exists(Opcode opcode, Class baseClass, Class derivedClass, ValueNumber operand | + inheritanceConversionValueNumber(instr, funcIR, opcode, baseClass, derivedClass, + operand) and + result = TInheritanceConversionValueNumber(funcIR, opcode, baseClass, derivedClass, operand) + ) or exists(Opcode opcode, Type type, int elementSize, ValueNumber leftOperand, ValueNumber rightOperand | pointerArithmeticValueNumber(instr, funcIR, opcode, type, elementSize, leftOperand, diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.expected index 9969baf40a8..d7c3740aed3 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.expected +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.expected @@ -27,3 +27,6 @@ | test.cpp:62:5:62:10 | result | 62:c5-c10 65:c10-c15 | | test.cpp:77:20:77:30 | (signed short)... | 77:c20-c30 79:c7-c7 | | test.cpp:79:11:79:14 | vals | 79:c11-c14 79:c24-c27 | +| test.cpp:105:11:105:12 | (Base *)... | 105:c11-c12 106:c14-c35 107:c11-c12 | +| test.cpp:105:11:105:12 | pd | 105:c11-c12 106:c33-c34 | +| test.cpp:105:15:105:15 | b | 105:c15-c15 107:c15-c15 109:c10-c10 | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected index 35b507a61f1..3df04157bf6 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected @@ -653,3 +653,62 @@ test.cpp: # 91| v0_12(void) = ReturnValue : r0_11, m0_10 # 91| v0_13(void) = UnmodeledUse : mu* # 91| v0_14(void) = ExitFunction : + +# 104| inheritanceConversions(Derived *) -> int +# 104| Block 0 +# 104| v0_0(void) = EnterFunction : +# 104| mu0_1(unknown) = UnmodeledDefinition : +# 104| valnum = unique +# 104| r0_2(glval) = VariableAddress[pd] : +# 104| valnum = r0_2 +# 104| m0_3(Derived *) = InitializeParameter[pd] : r0_2 +# 104| valnum = m0_3 +# 105| r0_4(glval) = VariableAddress[x] : +# 105| valnum = unique +# 105| r0_5(glval) = VariableAddress[pd] : +# 105| valnum = r0_2 +# 105| r0_6(Derived *) = Load : r0_5, m0_3 +# 105| valnum = m0_3 +# 105| r0_7(Base *) = ConvertToBase[Derived : Base] : r0_6 +# 105| valnum = r0_7 +# 105| r0_8(glval) = FieldAddress[b] : r0_7 +# 105| valnum = r0_8 +# 105| r0_9(int) = Load : r0_8, mu0_1 +# 105| valnum = r0_9 +# 105| m0_10(int) = Store : r0_4, r0_9 +# 105| valnum = r0_9 +# 106| r0_11(glval) = VariableAddress[pb] : +# 106| valnum = r0_11 +# 106| r0_12(glval) = VariableAddress[pd] : +# 106| valnum = r0_2 +# 106| r0_13(Derived *) = Load : r0_12, m0_3 +# 106| valnum = m0_3 +# 106| r0_14(Base *) = ConvertToBase[Derived : Base] : r0_13 +# 106| valnum = r0_7 +# 106| m0_15(Base *) = Store : r0_11, r0_14 +# 106| valnum = r0_7 +# 107| r0_16(glval) = VariableAddress[y] : +# 107| valnum = r0_16 +# 107| r0_17(glval) = VariableAddress[pb] : +# 107| valnum = r0_11 +# 107| r0_18(Base *) = Load : r0_17, m0_15 +# 107| valnum = r0_7 +# 107| r0_19(glval) = FieldAddress[b] : r0_18 +# 107| valnum = r0_8 +# 107| r0_20(int) = Load : r0_19, mu0_1 +# 107| valnum = r0_20 +# 107| m0_21(int) = Store : r0_16, r0_20 +# 107| valnum = r0_20 +# 109| r0_22(glval) = VariableAddress[#return] : +# 109| valnum = r0_22 +# 109| r0_23(glval) = VariableAddress[y] : +# 109| valnum = r0_16 +# 109| r0_24(int) = Load : r0_23, m0_21 +# 109| valnum = r0_20 +# 109| m0_25(int) = Store : r0_22, r0_24 +# 109| valnum = r0_20 +# 104| r0_26(glval) = VariableAddress[#return] : +# 104| valnum = r0_22 +# 104| v0_27(void) = ReturnValue : r0_26, m0_25 +# 104| v0_28(void) = UnmodeledUse : mu* +# 104| v0_29(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/test.cpp b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/test.cpp index 9c4a8bdd254..2003a299aab 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/test.cpp +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/test.cpp @@ -92,3 +92,19 @@ int regression_test00() { int x = x = 10; return x; } + +struct Base { + int b; +}; + +struct Derived : Base { + int d; +}; + +int inheritanceConversions(Derived* pd) { + int x = pd->b; + Base* pb = static_cast(pd); + int y = pb->b; + + return y; +} \ No newline at end of file From 5a25602c28a95987ec2fbcc8738fd6e9a6c7e5c4 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Thu, 20 Sep 2018 08:21:15 -0700 Subject: [PATCH 73/73] C++: Move GVN out of "internal" directory --- config/identical-files.json | 6 +++--- cpp/ql/src/semmle/code/cpp/ir/ValueNumbering.qll | 1 + .../gvn/ValueNumber.qll => gvn/ValueNumbering.qll} | 2 +- .../internal/ValueNumberingInternal.qll} | 0 .../gvn/ValueNumber.qll => gvn/ValueNumbering.qll} | 2 +- .../internal/ValueNumberingInternal.qll} | 0 .../gvn/ValueNumber.qll => gvn/ValueNumbering.qll} | 2 +- .../internal/ValueNumberingInternal.qll} | 0 .../valuenumbering/GlobalValueNumbering/ir_gvn.ql | 2 +- 9 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 cpp/ql/src/semmle/code/cpp/ir/ValueNumbering.qll rename cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/{internal/gvn/ValueNumber.qll => gvn/ValueNumbering.qll} (99%) rename cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/{internal/gvn/ValueNumberInternal.qll => gvn/internal/ValueNumberingInternal.qll} (100%) rename cpp/ql/src/semmle/code/cpp/ir/implementation/raw/{internal/gvn/ValueNumber.qll => gvn/ValueNumbering.qll} (99%) rename cpp/ql/src/semmle/code/cpp/ir/implementation/raw/{internal/gvn/ValueNumberInternal.qll => gvn/internal/ValueNumberingInternal.qll} (100%) rename cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/{internal/gvn/ValueNumber.qll => gvn/ValueNumbering.qll} (99%) rename cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/{internal/gvn/ValueNumberInternal.qll => gvn/internal/ValueNumberingInternal.qll} (100%) diff --git a/config/identical-files.json b/config/identical-files.json index c7c6ba4bc01..829837ceed4 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -56,8 +56,8 @@ "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll" ], "C++ IR ValueNumber": [ - "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll", - "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll", - "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll" + "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll", + "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll", + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll" ] } diff --git a/cpp/ql/src/semmle/code/cpp/ir/ValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/ValueNumbering.qll new file mode 100644 index 00000000000..bd02afc58fb --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/ValueNumbering.qll @@ -0,0 +1 @@ +import implementation.aliased_ssa.gvn.ValueNumbering diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll similarity index 99% rename from cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll rename to cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll index 1f91e6cac1a..cff349a07b4 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumber.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll @@ -1,4 +1,4 @@ -private import ValueNumberInternal +private import internal.ValueNumberingInternal import cpp private import IR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumberInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll similarity index 100% rename from cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/gvn/ValueNumberInternal.qll rename to cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll similarity index 99% rename from cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll rename to cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll index 1f91e6cac1a..cff349a07b4 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumber.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll @@ -1,4 +1,4 @@ -private import ValueNumberInternal +private import internal.ValueNumberingInternal import cpp private import IR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumberInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll similarity index 100% rename from cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/gvn/ValueNumberInternal.qll rename to cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll similarity index 99% rename from cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll rename to cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll index 1f91e6cac1a..cff349a07b4 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumber.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll @@ -1,4 +1,4 @@ -private import ValueNumberInternal +private import internal.ValueNumberingInternal import cpp private import IR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumberInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll similarity index 100% rename from cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/gvn/ValueNumberInternal.qll rename to cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql index 2b9521af1ae..97d59c73331 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.ql @@ -3,4 +3,4 @@ */ import semmle.code.cpp.ir.PrintIR import semmle.code.cpp.ir.IR -import semmle.code.cpp.ir.implementation.aliased_ssa.internal.gvn.ValueNumber +import semmle.code.cpp.ir.ValueNumbering