diff --git a/javascript/ql/src/semmle/javascript/Expr.qll b/javascript/ql/src/semmle/javascript/Expr.qll index 46b7f5e21d2..652b60eac0a 100644 --- a/javascript/ql/src/semmle/javascript/Expr.qll +++ b/javascript/ql/src/semmle/javascript/Expr.qll @@ -1545,17 +1545,24 @@ private string getConstantString(Expr e) { result = e.(TemplateElement).getValue() } +/** + * Holds if `add` is a string-concatenation where all the transitive leafs have a constant string value. + */ +private predicate hasAllConstantLeafs(AddExpr add) { + forex(Expr leaf | leaf = getAnAddOperand*(add) and not exists(getAnAddOperand(leaf)) | + exists(getConstantString(leaf)) + ) +} + /** * Gets the concatenated string for a string-concatenation `add`. - * Only has a result if `add` is not itself an operand in another string-concatenation. + * Only has a result if `add` is not itself an operand in another string-concatenation with all constant leafs. */ private string getConcatenatedString(Expr add) { result = getConcatenatedString(add.getUnderlyingValue()) or - not add = getAnAddOperand(_) and - forex(Expr leaf | leaf = getAnAddOperand*(add) and not exists(getAnAddOperand(leaf)) | - exists(getConstantString(leaf)) - ) and + not add = getAnAddOperand(any(AddExpr parent | hasAllConstantLeafs(parent))) and + hasAllConstantLeafs(add) and result = strictconcat(Expr leaf | leaf = getAnAddOperand*(add) diff --git a/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected b/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected index 0afbad59a08..936c19c59f8 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected @@ -46,6 +46,12 @@ concatenation | tst.js:89:3:89:14 | x | | tst.js:89:3:89:14 | x += 'three' | | tst.js:95:7:95:30 | x.conca ... three') | +| tst.js:104:11:104:23 | "foo" + "bar" | +| tst.js:104:11:104:31 | "foo" + ... + value | +| tst.js:105:11:105:23 | value + "foo" | +| tst.js:105:11:105:31 | value + ... + "bar" | +| tst.js:106:11:106:33 | "foo" + ... "baz") | +| tst.js:106:20:106:32 | "bar" + "baz" | concatenationOperand | closure.js:5:1:5:37 | build(' ... 'four') | | closure.js:5:7:5:11 | 'one' | @@ -127,6 +133,18 @@ concatenationOperand | tst.js:95:7:95:7 | x | | tst.js:95:16:95:20 | 'two' | | tst.js:95:23:95:29 | 'three' | +| tst.js:104:11:104:15 | "foo" | +| tst.js:104:11:104:23 | "foo" + "bar" | +| tst.js:104:19:104:23 | "bar" | +| tst.js:104:27:104:31 | value | +| tst.js:105:11:105:15 | value | +| tst.js:105:11:105:23 | value + "foo" | +| tst.js:105:19:105:23 | "foo" | +| tst.js:105:27:105:31 | "bar" | +| tst.js:106:11:106:15 | "foo" | +| tst.js:106:19:106:33 | ("bar" + "baz") | +| tst.js:106:20:106:24 | "bar" | +| tst.js:106:28:106:32 | "baz" | concatenationLeaf | closure.js:5:7:5:11 | 'one' | | closure.js:5:14:5:18 | 'two' | @@ -199,6 +217,16 @@ concatenationLeaf | tst.js:95:7:95:7 | x | | tst.js:95:16:95:20 | 'two' | | tst.js:95:23:95:29 | 'three' | +| tst.js:104:11:104:15 | "foo" | +| tst.js:104:19:104:23 | "bar" | +| tst.js:104:27:104:31 | value | +| tst.js:105:11:105:15 | value | +| tst.js:105:19:105:23 | "foo" | +| tst.js:105:27:105:31 | "bar" | +| tst.js:106:11:106:15 | "foo" | +| tst.js:106:19:106:33 | ("bar" + "baz") | +| tst.js:106:20:106:24 | "bar" | +| tst.js:106:28:106:32 | "baz" | concatenationNode | closure.js:5:1:5:37 | build(' ... 'four') | | closure.js:5:1:5:46 | build(' ... 'five' | @@ -318,6 +346,22 @@ concatenationNode | tst.js:95:7:95:30 | x.conca ... three') | | tst.js:95:16:95:20 | 'two' | | tst.js:95:23:95:29 | 'three' | +| tst.js:104:11:104:15 | "foo" | +| tst.js:104:11:104:23 | "foo" + "bar" | +| tst.js:104:11:104:31 | "foo" + ... + value | +| tst.js:104:19:104:23 | "bar" | +| tst.js:104:27:104:31 | value | +| tst.js:105:11:105:15 | value | +| tst.js:105:11:105:23 | value + "foo" | +| tst.js:105:11:105:31 | value + ... + "bar" | +| tst.js:105:19:105:23 | "foo" | +| tst.js:105:27:105:31 | "bar" | +| tst.js:106:11:106:15 | "foo" | +| tst.js:106:11:106:33 | "foo" + ... "baz") | +| tst.js:106:19:106:33 | ("bar" + "baz") | +| tst.js:106:20:106:24 | "bar" | +| tst.js:106:20:106:32 | "bar" + "baz" | +| tst.js:106:28:106:32 | "baz" | operand | closure.js:5:1:5:37 | build(' ... 'four') | 0 | closure.js:5:7:5:11 | 'one' | | closure.js:5:1:5:37 | build(' ... 'four') | 1 | closure.js:5:14:5:28 | 'two' + 'three' | @@ -421,6 +465,18 @@ operand | tst.js:95:7:95:30 | x.conca ... three') | 0 | tst.js:95:7:95:7 | x | | tst.js:95:7:95:30 | x.conca ... three') | 1 | tst.js:95:16:95:20 | 'two' | | tst.js:95:7:95:30 | x.conca ... three') | 2 | tst.js:95:23:95:29 | 'three' | +| tst.js:104:11:104:23 | "foo" + "bar" | 0 | tst.js:104:11:104:15 | "foo" | +| tst.js:104:11:104:23 | "foo" + "bar" | 1 | tst.js:104:19:104:23 | "bar" | +| tst.js:104:11:104:31 | "foo" + ... + value | 0 | tst.js:104:11:104:23 | "foo" + "bar" | +| tst.js:104:11:104:31 | "foo" + ... + value | 1 | tst.js:104:27:104:31 | value | +| tst.js:105:11:105:23 | value + "foo" | 0 | tst.js:105:11:105:15 | value | +| tst.js:105:11:105:23 | value + "foo" | 1 | tst.js:105:19:105:23 | "foo" | +| tst.js:105:11:105:31 | value + ... + "bar" | 0 | tst.js:105:11:105:23 | value + "foo" | +| tst.js:105:11:105:31 | value + ... + "bar" | 1 | tst.js:105:27:105:31 | "bar" | +| tst.js:106:11:106:33 | "foo" + ... "baz") | 0 | tst.js:106:11:106:15 | "foo" | +| tst.js:106:11:106:33 | "foo" + ... "baz") | 1 | tst.js:106:19:106:33 | ("bar" + "baz") | +| tst.js:106:20:106:32 | "bar" + "baz" | 0 | tst.js:106:20:106:24 | "bar" | +| tst.js:106:20:106:32 | "bar" + "baz" | 1 | tst.js:106:28:106:32 | "baz" | nextLeaf | closure.js:5:7:5:11 | 'one' | closure.js:5:14:5:18 | 'two' | | closure.js:5:14:5:18 | 'two' | closure.js:5:22:5:28 | 'three' | @@ -466,6 +522,12 @@ nextLeaf | tst.js:89:3:89:3 | x | tst.js:89:8:89:14 | 'three' | | tst.js:95:7:95:7 | x | tst.js:95:16:95:20 | 'two' | | tst.js:95:16:95:20 | 'two' | tst.js:95:23:95:29 | 'three' | +| tst.js:104:11:104:15 | "foo" | tst.js:104:19:104:23 | "bar" | +| tst.js:104:19:104:23 | "bar" | tst.js:104:27:104:31 | value | +| tst.js:105:11:105:15 | value | tst.js:105:19:105:23 | "foo" | +| tst.js:105:19:105:23 | "foo" | tst.js:105:27:105:31 | "bar" | +| tst.js:106:11:106:15 | "foo" | tst.js:106:19:106:33 | ("bar" + "baz") | +| tst.js:106:20:106:24 | "bar" | tst.js:106:28:106:32 | "baz" | htmlRoot | html-concat.js:2:14:2:26 | `${x}` | | html-concat.js:3:14:3:26 | `${x}` | @@ -488,3 +550,13 @@ htmlLeaf | html-concat.js:8:15:10:23 | .\\n \\n ... um! | | html-concat.js:13:3:13:8 | buffer | | html-concat.js:13:13:13:18 | '
  • ' | +getStringValue +| tst.js:104:11:104:15 | "foo" | foo | +| tst.js:104:11:104:23 | "foo" + "bar" | foobar | +| tst.js:104:19:104:23 | "bar" | bar | +| tst.js:105:19:105:23 | "foo" | foo | +| tst.js:105:27:105:31 | "bar" | bar | +| tst.js:106:11:106:15 | "foo" | foo | +| tst.js:106:11:106:33 | "foo" + ... "baz") | foobarbaz | +| tst.js:106:20:106:24 | "bar" | bar | +| tst.js:106:28:106:32 | "baz" | baz | diff --git a/javascript/ql/test/library-tests/StringConcatenation/StringOps.ql b/javascript/ql/test/library-tests/StringConcatenation/StringOps.ql index 8b0d96a714e..38066fcf175 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/StringOps.ql +++ b/javascript/ql/test/library-tests/StringConcatenation/StringOps.ql @@ -19,3 +19,8 @@ query predicate nextLeaf(StringOps::ConcatenationNode node, DataFlow::Node next) query StringOps::HtmlConcatenationRoot htmlRoot() { any() } query StringOps::HtmlConcatenationLeaf htmlLeaf() { any() } + +query string getStringValue(Expr e) { + result = e.getStringValue() and + e.getEnclosingFunction().getName() = "stringValue" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/StringConcatenation/tst.js b/javascript/ql/test/library-tests/StringConcatenation/tst.js index d1e70fb7239..e6ddd1234d0 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/tst.js +++ b/javascript/ql/test/library-tests/StringConcatenation/tst.js @@ -99,3 +99,9 @@ function concatCall() { function arrayConcat(a, b) { return [].concat(a, b); } + +function stringValue() { + var a = "foo" + "bar" + value; + var b = value + "foo" + "bar"; + var c = "foo" + ("bar" + "baz") +} \ No newline at end of file