From 5040d3e26c73eb5ad698657da90c594fde4f1d24 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Fri, 14 Dec 2018 15:39:09 +0000
Subject: [PATCH 01/10] JS: add query for loop index bug
---
...djustmentAfterConcurrentModification.qhelp | 68 ++++++++
...exAdjustmentAfterConcurrentModification.ql | 155 ++++++++++++++++++
...exAdjustmentAfterConcurrentModification.js | 9 +
...justmentAfterConcurrentModificationGood.js | 10 ++
...ntAfterConcurrentModificationGoodFilter.js | 3 +
...stmentAfterConcurrentModification.expected | 3 +
...djustmentAfterConcurrentModification.qlref | 1 +
.../tst.js | 113 +++++++++++++
8 files changed, 362 insertions(+)
create mode 100644 javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
create mode 100644 javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
create mode 100644 javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModification.js
create mode 100644 javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGood.js
create mode 100644 javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGoodFilter.js
create mode 100644 javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
create mode 100644 javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref
create mode 100644 javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
new file mode 100644
index 00000000000..20bf93105d3
--- /dev/null
+++ b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
@@ -0,0 +1,68 @@
+
+
+
+
+ Items can be removed from an array using the splice method, but when doing so,
+ all subseequent items will be shifted to a lower index. If this is done while iterating over
+ the array, the shifting may cause the loop to skip over the element immediately after the
+ removed element.
+
+
+
+
+
+
+Determine what the loop is supposed to do:
+
+ -
+ If the intention is to remove every occurence of a certain value, decrement the loop counter after removing an element, to counterbalance
+ the shift.
+
+ -
+ If the loop is only intended to remove a single value from the array, consider adding a
break after the splice call.
+
+ -
+ If the loop is deliberately skipping over elements, consider moving the index increment into the body of the loop,
+ so it is clear that the loop is not a trivial array iteration loop.
+
+
+
+
+
+
+
+
+In this example, a function is intended to remove ".." parts from a path:
+
+
+
+
+
+However, whenever the input contain two ".." parts right after one another, only the first will be removed.
+For example, the string "../../secret.txt" will be mapped to "../secret.txt". After removing
+the element at index 0, the loop counter is incremented to 1, but the second ".." string has now been shifted down to
+index 0 and will therefore be skipped.
+
+
+
+One way to avoid this is to decrement the loop counter after removing an element from the array:
+
+
+
+
+
+Alternatively, use the filter method:
+
+
+
+
+
+
+
+MDN: Array.prototype.splice().
+MDN: Array.prototype.filter().
+
+
+
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
new file mode 100644
index 00000000000..23f19afc127
--- /dev/null
+++ b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
@@ -0,0 +1,155 @@
+/**
+ * @name Missing index adjustment after concurrent modification
+ * @description Removing elements from an array while iterating over it can cause the loop to skip over some elements,
+ * unless the loop index is decremented accordingly.
+ * @kind problem
+ * @problem.severity warning
+ * @id js/missing-index-adjustment-after-concurrent-modification
+ * @tags correctness
+ * @precision high
+ */
+import javascript
+
+/**
+ * Operation that inserts or removes elements from an array while shifting all elements
+ * occuring after the insertion/removal point.
+ *
+ * Does not include `push` and `pop` since these never shift any elements.
+ */
+class ArrayShiftingCall extends DataFlow::MethodCallNode {
+ string name;
+
+ ArrayShiftingCall() {
+ name = getMethodName() and
+ (name = "splice" or name = "shift" or name = "unshift")
+ }
+
+ DataFlow::SourceNode getArray() {
+ result = getReceiver().getALocalSource()
+ }
+}
+
+/**
+ * A call to `splice` on an array.
+ */
+class SpliceCall extends ArrayShiftingCall {
+ SpliceCall() {
+ name = "splice"
+ }
+
+ /**
+ * Gets the index from which elements are removed and possibly new elemenst are inserted.
+ */
+ DataFlow::Node getIndex() {
+ result = getArgument(0)
+ }
+
+ /**
+ * Gets the number of removed elements.
+ */
+ int getNumRemovedElements() {
+ result = getArgument(1).asExpr().getIntValue() and
+ result >= 0
+ }
+
+ /**
+ * Gets the number of inserted elements.
+ */
+ int getNumInsertedElements() {
+ result = getNumArgument() - 2 and
+ result >= 0
+ }
+}
+
+/**
+ * A `for` loop iterating over the indices of an array, in increasing order.
+ */
+class ArrayIterationLoop extends ForStmt {
+ DataFlow::SourceNode array;
+ LocalVariable indexVariable;
+
+ ArrayIterationLoop() {
+ exists (RelationalComparison compare | compare = getTest() |
+ compare.getLesserOperand() = indexVariable.getAnAccess() and
+ compare.getGreaterOperand() = array.getAPropertyRead("length").asExpr()) and
+ getUpdate().(IncExpr).getOperand() = indexVariable.getAnAccess()
+ }
+
+ /**
+ * Gets the variable holding the loop variable and current array index.
+ */
+ LocalVariable getIndexVariable() {
+ result = indexVariable
+ }
+
+ /**
+ * Gets the loop entry point.
+ */
+ ReachableBasicBlock getLoopEntry() {
+ result = getTest().getFirstControlFlowNode().getBasicBlock()
+ }
+
+ /**
+ * Gets a call that potentially shifts the elements of the given array.
+ */
+ ArrayShiftingCall getAnArrayShiftingCall() {
+ result.getArray() = array
+ }
+
+ /**
+ * Gets a call to `splice` that removes elements from the looped-over array at the current index
+ *
+ * The `splice` call is not guaranteed to be inside the loop body.
+ */
+ SpliceCall getACandidateSpliceCall() {
+ result = getAnArrayShiftingCall() and
+ result.getIndex().asExpr() = getIndexVariable().getAnAccess() and
+ result.getNumRemovedElements() > result.getNumInsertedElements()
+ }
+
+ /**
+ * Holds if `cfg` modifies the index variable or shifts array elements, disturbing the
+ * relationship between the array and the index variable.
+ */
+ predicate hasIndexingManipulation(ControlFlowNode cfg) {
+ cfg.(VarDef).getAVariable() = getIndexVariable() or
+ cfg = getAnArrayShiftingCall().asExpr()
+ }
+
+ /**
+ * Holds if there is a `loop entry -> cfg` path that does not involve index manipulation or a successful index equality check.
+ */
+ predicate hasPathTo(ControlFlowNode cfg) {
+ exists(getACandidateSpliceCall()) and // restrict size of predicate
+ cfg = getLoopEntry().getFirstNode()
+ or
+ hasPathTo(cfg.getAPredecessor()) and
+ getLoopEntry().dominates(cfg.getBasicBlock()) and
+ not hasIndexingManipulation(cfg) and
+ // Ignore splice calls guarded by an index equality check.
+ // This indicates that the index of an element is the basis for removal, not its value,
+ // which means it may be okay to skip over elements.
+ not exists (ConditionGuardNode guard, EqualityTest test | cfg = guard |
+ test = guard.getTest() and
+ test.getAnOperand() = getIndexVariable().getAnAccess() and
+ guard.getOutcome() = test.getPolarity())
+ }
+
+ /**
+ * Holds if there is a `loop entry -> splice -> cfg` path that does not involve index manipulation,
+ * other than the `splice` call.
+ */
+ predicate hasPathThrough(SpliceCall splice, ControlFlowNode cfg) {
+ splice = getACandidateSpliceCall() and
+ cfg = splice.asExpr() and
+ hasPathTo(cfg.getAPredecessor())
+ or
+ hasPathThrough(splice, cfg.getAPredecessor()) and
+ getLoopEntry().dominates(cfg.getBasicBlock()) and
+ not hasIndexingManipulation(cfg)
+ }
+}
+
+from ArrayIterationLoop loop, SpliceCall splice
+where loop.hasPathThrough(splice, loop.getUpdate().getFirstControlFlowNode())
+select splice, "Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting."
diff --git a/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModification.js b/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModification.js
new file mode 100644
index 00000000000..ccff5c5d208
--- /dev/null
+++ b/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModification.js
@@ -0,0 +1,9 @@
+function removePathTraversal(path) {
+ let parts = path.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === '..') {
+ parts.splice(i, 1);
+ }
+ }
+ return path.join('/');
+}
diff --git a/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGood.js b/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGood.js
new file mode 100644
index 00000000000..3ebedc7ced9
--- /dev/null
+++ b/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGood.js
@@ -0,0 +1,10 @@
+function removePathTraversal(path) {
+ let parts = path.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === '..') {
+ parts.splice(i, 1);
+ --i; // adjust for array shift
+ }
+ }
+ return path.join('/');
+}
diff --git a/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGoodFilter.js b/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGoodFilter.js
new file mode 100644
index 00000000000..eba08d045d2
--- /dev/null
+++ b/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGoodFilter.js
@@ -0,0 +1,3 @@
+function removePathTraversal(path) {
+ return path.split('/').filter(part => part !== '..').join('/');
+}
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
new file mode 100644
index 00000000000..8348040e873
--- /dev/null
+++ b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
@@ -0,0 +1,3 @@
+| tst.js:4:27:4:44 | parts.splice(i, 1) | Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting. |
+| tst.js:13:29:13:46 | parts.splice(i, 1) | Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting. |
+| tst.js:24:9:24:26 | parts.splice(i, 1) | Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting. |
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref
new file mode 100644
index 00000000000..0367f1f3887
--- /dev/null
+++ b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref
@@ -0,0 +1 @@
+Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
\ No newline at end of file
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js
new file mode 100644
index 00000000000..6de02e2c574
--- /dev/null
+++ b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js
@@ -0,0 +1,113 @@
+function removeX(string) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === 'X') parts.splice(i, 1); // NOT OK
+ }
+ return parts.join('/');
+}
+
+function removeXInnerLoop(string, n) {
+ let parts = string.split('/');
+ for (let j = 0; j < n; ++j) {
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === 'X') parts.splice(i, 1); // NOT OK
+ }
+ }
+ return parts.join('/');
+}
+
+function removeXOuterLoop(string, n) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ for (let j = 0; j < n; ++j) {
+ if (parts[i] === 'X') {
+ parts.splice(i, 1); // NOT OK
+ break;
+ }
+ }
+ }
+ return parts.join('/');
+}
+
+function decrementAfter(string) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === 'X') {
+ parts.splice(i, 1); // OK
+ --i;
+ }
+ }
+ return parts.join('/');
+}
+
+function postDecrementArgument(string) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === 'X') {
+ parts.splice(i--, 1); // OK
+ }
+ }
+ return parts.join('/');
+}
+
+
+function breakAfter(string) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === 'X') {
+ parts.splice(i, 1); // OK - only removes first occurrence
+ break;
+ }
+ }
+ return parts.join('/');
+}
+
+function insertNewElements(string) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (parts[i] === 'X') {
+ parts.splice(i, 1, '.'); // OK - no shifting due to insert
+ }
+ }
+ return parts.join('/');
+}
+
+function spliceAfterLoop(string) {
+ let parts = string.split('/');
+ let i = 0;
+ for (; i < parts.length; ++i) {
+ if (parts[i] === 'X') break;
+ }
+ if (parts[i] === 'X') {
+ parts.splice(i, 1); // OK - not inside loop
+ }
+ return parts.join('/');
+}
+
+function spliceAfterLoopNested(string) {
+ let parts = string.split('/');
+ for (let j = 0; j < parts.length; ++j) {
+ let i = j;
+ for (; i < parts.length; ++i) {
+ if (parts[i] === 'X') break;
+ }
+ parts.splice(i, 1); // OK - not inside 'i' loop
+ }
+ return parts.join('/');
+}
+
+function removeAtSpecificPlace(string, k) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (i === k && parts[i] === 'X') parts.splice(i, 1); // OK - more complex logic
+ }
+ return parts.join('/');
+}
+
+function removeFirstAndLast(string) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (i === 0 || i === parts.length - 1) parts.splice(i, 1); // OK - out of scope of this query
+ }
+ return parts.join('/');
+}
From 280382e91ecd071d18eb55b05f5d670130ac5b8c Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 17 Dec 2018 15:19:26 +0000
Subject: [PATCH 02/10] JS: whitelist if array access at another index is seen
---
...issingIndexAdjustmentAfterConcurrentModification.ql | 10 +++++++++-
.../tst.js | 10 ++++++++++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
index 23f19afc127..23bc456ce6e 100644
--- a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
+++ b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
@@ -126,13 +126,21 @@ class ArrayIterationLoop extends ForStmt {
hasPathTo(cfg.getAPredecessor()) and
getLoopEntry().dominates(cfg.getBasicBlock()) and
not hasIndexingManipulation(cfg) and
+
// Ignore splice calls guarded by an index equality check.
// This indicates that the index of an element is the basis for removal, not its value,
// which means it may be okay to skip over elements.
not exists (ConditionGuardNode guard, EqualityTest test | cfg = guard |
test = guard.getTest() and
test.getAnOperand() = getIndexVariable().getAnAccess() and
- guard.getOutcome() = test.getPolarity())
+ guard.getOutcome() = test.getPolarity()) and
+
+ // Block flow after inspecting an array element other than that at the current index.
+ // For example, if the splice happens after inspecting `array[i + 1]`, then the next
+ // element has already been "looked at" and so it doesn't matter if we skip it.
+ not exists (IndexExpr index | cfg = index |
+ array.flowsToExpr(index.getBase()) and
+ not index.getIndex() = getIndexVariable().getAnAccess())
}
/**
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js
index 6de02e2c574..cebe8569c08 100644
--- a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js
+++ b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js
@@ -111,3 +111,13 @@ function removeFirstAndLast(string) {
}
return parts.join('/');
}
+
+function inspectNextElement(string) {
+ let parts = string.split('/');
+ for (let i = 0; i < parts.length; ++i) {
+ if (i < parts.length - 1 && parts[i] === parts[i + 1]) {
+ parts.splice(i, 1); // OK - next element has been looked at
+ }
+ }
+ return parts.join('/');
+}
From d595f20cb1971f3357e55881ab255646a52beb13 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 17 Dec 2018 15:29:10 +0000
Subject: [PATCH 03/10] JS: add to correctness-more suite
---
javascript/config/suites/javascript/correctness-more | 1 +
1 file changed, 1 insertion(+)
diff --git a/javascript/config/suites/javascript/correctness-more b/javascript/config/suites/javascript/correctness-more
index 2e57bcd7327..540f88387b6 100644
--- a/javascript/config/suites/javascript/correctness-more
+++ b/javascript/config/suites/javascript/correctness-more
@@ -12,6 +12,7 @@
+ semmlecode-javascript-queries/LanguageFeatures/InconsistentNew.ql: /Correctness/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/SpuriousArguments.ql: /Correctness/Language Features
+ semmlecode-javascript-queries/Statements/MisleadingIndentationAfterControlStmt.ql: /Correctness/Statements
++ semmlecode-javascript-queries/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql: /Correctness/Statements
+ semmlecode-javascript-queries/Statements/ReturnOutsideFunction.ql: /Correctness/Statements
+ semmlecode-javascript-queries/Statements/SuspiciousUnusedLoopIterationVariable.ql: /Correctness/Statements
+ semmlecode-javascript-queries/Statements/UselessConditional.ql: /Correctness/Statements
From e1c25c81f657a41789e4f385030deaafd13175e7 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 17 Dec 2018 16:34:18 +0000
Subject: [PATCH 04/10] JS: add change note
---
change-notes/1.20/analysis-javascript.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/change-notes/1.20/analysis-javascript.md b/change-notes/1.20/analysis-javascript.md
index 14a4104c7dd..f31fd00787e 100644
--- a/change-notes/1.20/analysis-javascript.md
+++ b/change-notes/1.20/analysis-javascript.md
@@ -13,6 +13,7 @@
| Double escaping or unescaping (`js/double-escaping`) | correctness, security, external/cwe/cwe-116 | Highlights potential double escaping or unescaping of special characters, indicating a possible violation of [CWE-116](https://cwe.mitre.org/data/definitions/116.html). Results are shown on LGTM by default. |
| Incomplete URL substring sanitization | correctness, security, external/cwe/cwe-020 | Highlights URL sanitizers that are likely to be incomplete, indicating a violation of [CWE-020](https://cwe.mitre.org/data/definitions/20.html). Results shown on LGTM by default. |
| Incorrect suffix check (`js/incorrect-suffix-check`) | correctness, security, external/cwe/cwe-020 | Highlights error-prone suffix checks based on `indexOf`, indicating a potential violation of [CWE-20](https://cwe.mitre.org/data/definitions/20.html). Results are shown on LGTM by default. |
+| Missing index adjustment after concurrent modification (`js/missing-index-adjustment-after-concurrent-modification`) | correctness | Highlights code that removes an element from an array while iterating over it, causing the loop to skip over some elements. Results are shown on LGTM by default. |
| Useless comparison test (`js/useless-comparison-test`) | correctness | Highlights code that is unreachable due to a numeric comparison that is always true or always false. Results are shown on LGTM by default. |
## Changes to existing queries
From f57454951bc491d3335f7f6b426cadf583dd6ccc Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 18 Dec 2018 14:15:12 +0000
Subject: [PATCH 05/10] JS: move outside of element
---
.../MissingIndexAdjustmentAfterConcurrentModification.qhelp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
index 20bf93105d3..3340b919ce2 100644
--- a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
+++ b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
@@ -15,6 +15,8 @@
Determine what the loop is supposed to do:
+
+
-
If the intention is to remove every occurence of a certain value, decrement the loop counter after removing an element, to counterbalance
@@ -28,7 +30,6 @@ Determine what the loop is supposed to do:
so it is clear that the loop is not a trivial array iteration loop.
-
From f9d7f8ba11be7cd9fac14eefa67c4831a5408c0d Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 19 Dec 2018 10:10:56 +0000
Subject: [PATCH 06/10] JS: fix links in qhelp
---
.../MissingIndexAdjustmentAfterConcurrentModification.qhelp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
index 3340b919ce2..b682eab0688 100644
--- a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
+++ b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
@@ -38,7 +38,7 @@ Determine what the loop is supposed to do:
In this example, a function is intended to remove ".." parts from a path:
-
+
However, whenever the input contain two ".." parts right after one another, only the first will be removed.
@@ -51,13 +51,13 @@ index 0 and will therefore be skipped.
One way to avoid this is to decrement the loop counter after removing an element from the array:
-
+
Alternatively, use the filter method:
-
+
From 8c3b44a5256411309062ad30ad17e77e5fa5aea5 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 2 Jan 2019 11:12:52 +0000
Subject: [PATCH 07/10] JS: address comments
---
.../MissingIndexAdjustmentAfterConcurrentModification.qhelp | 2 +-
.../MissingIndexAdjustmentAfterConcurrentModification.ql | 4 ++--
...ssingIndexAdjustmentAfterConcurrentModification.expected | 6 +++---
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
index b682eab0688..6f85a9269dd 100644
--- a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
+++ b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
@@ -5,7 +5,7 @@
Items can be removed from an array using the splice method, but when doing so,
- all subseequent items will be shifted to a lower index. If this is done while iterating over
+ all subsequent items will be shifted to a lower index. If this is done while iterating over
the array, the shifting may cause the loop to skip over the element immediately after the
removed element.
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
index 23bc456ce6e..18d302cb8da 100644
--- a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
+++ b/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
@@ -11,7 +11,7 @@
import javascript
/**
- * Operation that inserts or removes elements from an array while shifting all elements
+ * An operation that inserts or removes elements from an array while shifting all elements
* occuring after the insertion/removal point.
*
* Does not include `push` and `pop` since these never shift any elements.
@@ -160,4 +160,4 @@ class ArrayIterationLoop extends ForStmt {
from ArrayIterationLoop loop, SpliceCall splice
where loop.hasPathThrough(splice, loop.getUpdate().getFirstControlFlowNode())
-select splice, "Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting."
+select splice, "Removing an array item without adjusting the loop index '" + loop.getIndexVariable().getName() + "' causes the subsequent array item to be skipped."
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
index 8348040e873..ae29da4a00c 100644
--- a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
+++ b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
@@ -1,3 +1,3 @@
-| tst.js:4:27:4:44 | parts.splice(i, 1) | Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting. |
-| tst.js:13:29:13:46 | parts.splice(i, 1) | Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting. |
-| tst.js:24:9:24:26 | parts.splice(i, 1) | Missing loop index adjustment after removing array item. Some array items will be skipped due to shifting. |
+| tst.js:4:27:4:44 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent item to be skipped. |
+| tst.js:13:29:13:46 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent item to be skipped. |
+| tst.js:24:9:24:26 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent item to be skipped. |
From 9f22da455727ff298c505a5acd3ee2021c0466b9 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 2 Jan 2019 11:34:06 +0000
Subject: [PATCH 08/10] JS: rename query to "Loop iteration skipped due to
shifting"
---
change-notes/1.20/analysis-javascript.md | 2 +-
...cation.qhelp => LoopIterationSkippedDueToShifting.qhelp} | 6 +++---
...Modification.ql => LoopIterationSkippedDueToShifting.ql} | 4 ++--
...Modification.js => LoopIterationSkippedDueToShifting.js} | 0
...tionGood.js => LoopIterationSkippedDueToShiftingGood.js} | 0
...er.js => LoopIterationSkippedDueToShiftingGoodFilter.js} | 0
.../LoopIterationSkippedDueToShifting.expected | 3 +++
.../LoopIterationSkippedDueToShifting.qlref | 1 +
.../tst.js | 0
...ssingIndexAdjustmentAfterConcurrentModification.expected | 3 ---
.../MissingIndexAdjustmentAfterConcurrentModification.qlref | 1 -
11 files changed, 10 insertions(+), 10 deletions(-)
rename javascript/ql/src/Statements/{MissingIndexAdjustmentAfterConcurrentModification.qhelp => LoopIterationSkippedDueToShifting.qhelp} (89%)
rename javascript/ql/src/Statements/{MissingIndexAdjustmentAfterConcurrentModification.ql => LoopIterationSkippedDueToShifting.ql} (97%)
rename javascript/ql/src/Statements/examples/{MissingIndexAdjustmentAfterConcurrentModification.js => LoopIterationSkippedDueToShifting.js} (100%)
rename javascript/ql/src/Statements/examples/{MissingIndexAdjustmentAfterConcurrentModificationGood.js => LoopIterationSkippedDueToShiftingGood.js} (100%)
rename javascript/ql/src/Statements/examples/{MissingIndexAdjustmentAfterConcurrentModificationGoodFilter.js => LoopIterationSkippedDueToShiftingGoodFilter.js} (100%)
create mode 100644 javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.expected
create mode 100644 javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.qlref
rename javascript/ql/test/query-tests/Statements/{MissingIndexAdjustmentAfterConcurrentModification => LoopIterationSkippedDueToShifting}/tst.js (100%)
delete mode 100644 javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
delete mode 100644 javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref
diff --git a/change-notes/1.20/analysis-javascript.md b/change-notes/1.20/analysis-javascript.md
index f31fd00787e..3ca91c11bf7 100644
--- a/change-notes/1.20/analysis-javascript.md
+++ b/change-notes/1.20/analysis-javascript.md
@@ -13,7 +13,7 @@
| Double escaping or unescaping (`js/double-escaping`) | correctness, security, external/cwe/cwe-116 | Highlights potential double escaping or unescaping of special characters, indicating a possible violation of [CWE-116](https://cwe.mitre.org/data/definitions/116.html). Results are shown on LGTM by default. |
| Incomplete URL substring sanitization | correctness, security, external/cwe/cwe-020 | Highlights URL sanitizers that are likely to be incomplete, indicating a violation of [CWE-020](https://cwe.mitre.org/data/definitions/20.html). Results shown on LGTM by default. |
| Incorrect suffix check (`js/incorrect-suffix-check`) | correctness, security, external/cwe/cwe-020 | Highlights error-prone suffix checks based on `indexOf`, indicating a potential violation of [CWE-20](https://cwe.mitre.org/data/definitions/20.html). Results are shown on LGTM by default. |
-| Missing index adjustment after concurrent modification (`js/missing-index-adjustment-after-concurrent-modification`) | correctness | Highlights code that removes an element from an array while iterating over it, causing the loop to skip over some elements. Results are shown on LGTM by default. |
+| Loop iteration skipped due to shifting (`js/loop-iteration-skipped-due-to-shifting`) | correctness | Highlights code that removes an element from an array while iterating over it, causing the loop to skip over some elements. Results are shown on LGTM by default. |
| Useless comparison test (`js/useless-comparison-test`) | correctness | Highlights code that is unreachable due to a numeric comparison that is always true or always false. Results are shown on LGTM by default. |
## Changes to existing queries
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp
similarity index 89%
rename from javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
rename to javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp
index 6f85a9269dd..de62639a213 100644
--- a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.qhelp
+++ b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp
@@ -38,7 +38,7 @@ Determine what the loop is supposed to do:
In this example, a function is intended to remove ".." parts from a path:
-
+
However, whenever the input contain two ".." parts right after one another, only the first will be removed.
@@ -51,13 +51,13 @@ index 0 and will therefore be skipped.
One way to avoid this is to decrement the loop counter after removing an element from the array:
-
+
Alternatively, use the filter method:
-
+
diff --git a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql
similarity index 97%
rename from javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
rename to javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql
index 18d302cb8da..e15462b25e9 100644
--- a/javascript/ql/src/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
+++ b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql
@@ -1,10 +1,10 @@
/**
- * @name Missing index adjustment after concurrent modification
+ * @name Loop iteration skipped due to shifting.
* @description Removing elements from an array while iterating over it can cause the loop to skip over some elements,
* unless the loop index is decremented accordingly.
* @kind problem
* @problem.severity warning
- * @id js/missing-index-adjustment-after-concurrent-modification
+ * @id js/loop-iteration-skipped-due-to-shifting
* @tags correctness
* @precision high
*/
diff --git a/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModification.js b/javascript/ql/src/Statements/examples/LoopIterationSkippedDueToShifting.js
similarity index 100%
rename from javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModification.js
rename to javascript/ql/src/Statements/examples/LoopIterationSkippedDueToShifting.js
diff --git a/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGood.js b/javascript/ql/src/Statements/examples/LoopIterationSkippedDueToShiftingGood.js
similarity index 100%
rename from javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGood.js
rename to javascript/ql/src/Statements/examples/LoopIterationSkippedDueToShiftingGood.js
diff --git a/javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGoodFilter.js b/javascript/ql/src/Statements/examples/LoopIterationSkippedDueToShiftingGoodFilter.js
similarity index 100%
rename from javascript/ql/src/Statements/examples/MissingIndexAdjustmentAfterConcurrentModificationGoodFilter.js
rename to javascript/ql/src/Statements/examples/LoopIterationSkippedDueToShiftingGoodFilter.js
diff --git a/javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.expected b/javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.expected
new file mode 100644
index 00000000000..4b7becd8e16
--- /dev/null
+++ b/javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.expected
@@ -0,0 +1,3 @@
+| tst.js:4:27:4:44 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent array item to be skipped. |
+| tst.js:13:29:13:46 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent array item to be skipped. |
+| tst.js:24:9:24:26 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent array item to be skipped. |
diff --git a/javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.qlref b/javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.qlref
new file mode 100644
index 00000000000..796046d180a
--- /dev/null
+++ b/javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/LoopIterationSkippedDueToShifting.qlref
@@ -0,0 +1 @@
+Statements/LoopIterationSkippedDueToShifting.ql
\ No newline at end of file
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js b/javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/tst.js
similarity index 100%
rename from javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/tst.js
rename to javascript/ql/test/query-tests/Statements/LoopIterationSkippedDueToShifting/tst.js
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
deleted file mode 100644
index ae29da4a00c..00000000000
--- a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.expected
+++ /dev/null
@@ -1,3 +0,0 @@
-| tst.js:4:27:4:44 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent item to be skipped. |
-| tst.js:13:29:13:46 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent item to be skipped. |
-| tst.js:24:9:24:26 | parts.splice(i, 1) | Removing an array item without adjusting the loop index 'i' causes the subsequent item to be skipped. |
diff --git a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref b/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref
deleted file mode 100644
index 0367f1f3887..00000000000
--- a/javascript/ql/test/query-tests/Statements/MissingIndexAdjustmentAfterConcurrentModification/MissingIndexAdjustmentAfterConcurrentModification.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Statements/MissingIndexAdjustmentAfterConcurrentModification.ql
\ No newline at end of file
From bc59e65222fda49555c39498893dc905b0e1a478 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 2 Jan 2019 11:42:47 +0000
Subject: [PATCH 09/10] JS: update suite file
---
javascript/config/suites/javascript/correctness-more | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/config/suites/javascript/correctness-more b/javascript/config/suites/javascript/correctness-more
index 540f88387b6..8552456ec1e 100644
--- a/javascript/config/suites/javascript/correctness-more
+++ b/javascript/config/suites/javascript/correctness-more
@@ -11,8 +11,8 @@
+ semmlecode-javascript-queries/LanguageFeatures/IllegalInvocation.ql: /Correctness/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/InconsistentNew.ql: /Correctness/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/SpuriousArguments.ql: /Correctness/Language Features
++ semmlecode-javascript-queries/Statements/LoopIterationSkippedDueToShifting.ql: /Correctness/Statements
+ semmlecode-javascript-queries/Statements/MisleadingIndentationAfterControlStmt.ql: /Correctness/Statements
-+ semmlecode-javascript-queries/Statements/MissingIndexAdjustmentAfterConcurrentModification.ql: /Correctness/Statements
+ semmlecode-javascript-queries/Statements/ReturnOutsideFunction.ql: /Correctness/Statements
+ semmlecode-javascript-queries/Statements/SuspiciousUnusedLoopIterationVariable.ql: /Correctness/Statements
+ semmlecode-javascript-queries/Statements/UselessConditional.ql: /Correctness/Statements
From f24313a215e19e90a74626c0ce7c1cb6d134e93a Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 3 Jan 2019 10:49:36 +0000
Subject: [PATCH 10/10] JS: address doc review
---
.../ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp | 2 +-
.../ql/src/Statements/LoopIterationSkippedDueToShifting.ql | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp
index de62639a213..ef24e63fa5a 100644
--- a/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp
+++ b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.qhelp
@@ -19,7 +19,7 @@ Determine what the loop is supposed to do:
-
- If the intention is to remove every occurence of a certain value, decrement the loop counter after removing an element, to counterbalance
+ If the intention is to remove every occurrence of a certain value, decrement the loop counter after removing an element, to counterbalance
the shift.
-
diff --git a/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql
index e15462b25e9..bbaca72234e 100644
--- a/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql
+++ b/javascript/ql/src/Statements/LoopIterationSkippedDueToShifting.ql
@@ -1,5 +1,5 @@
/**
- * @name Loop iteration skipped due to shifting.
+ * @name Loop iteration skipped due to shifting
* @description Removing elements from an array while iterating over it can cause the loop to skip over some elements,
* unless the loop index is decremented accordingly.
* @kind problem