Skip to content

Commit 3d058a2

Browse files
authored
Merge pull request #603 from xiemaisi/js/fix-inconsistent-new
Approved by asger-semmle, esben-semmle
2 parents 436ee55 + 8627ddb commit 3d058a2

File tree

4 files changed

+59
-21
lines changed

4 files changed

+59
-21
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
| Conflicting HTML element attributes | Lower severity | The severity of this rule has been revised to "warning". |
4646
| Duplicate 'if' condition | Lower severity | The severity of this rule has been revised to "warning". |
4747
| Duplicate switch case | Lower severity | The severity of this rule has been revised to "warning". |
48+
| Inconsistent use of 'new' | Simpler result presentation | This rule now only shows one call with `new` and one without. |
4849
| Information exposure through a stack trace | More results | This rule now also flags cases where the entire exception object (including the stack trace) may be exposed. |
4950
| Missing 'this' qualifier | Fewer false-positive results | This rule now recognizes additional intentional calls to global functions. |
5051
| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. |

javascript/ql/src/LanguageFeatures/InconsistentNew.ql

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,26 +56,60 @@ predicate calls(DataFlow::InvokeNode cs, Function callee, int imprecision) {
5656

5757
/**
5858
* Gets a function that may be invoked at `cs`, preferring callees that
59-
* are less likely to be derived due to analysis imprecision.
59+
* are less likely to be derived due to analysis imprecision and excluding
60+
* whitelisted call sites and callees. Additionally, `isNew` is bound to
61+
* `true` if `cs` is a `new` expression, and to `false` otherwise.
6062
*/
61-
Function getALikelyCallee(DataFlow::InvokeNode cs) {
62-
calls(cs, result, min(int p | calls(cs, _, p)))
63+
Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) {
64+
calls(cs, result, min(int p | calls(cs, _, p))) and
65+
not cs.isUncertain() and
66+
not whitelistedCall(cs) and
67+
not whitelistedCallee(result) and
68+
(cs instanceof DataFlow::NewNode and isNew = true
69+
or
70+
cs instanceof DataFlow::CallNode and isNew = false)
71+
}
72+
73+
/**
74+
* Holds if `f` should be whitelisted, either because it guards against
75+
* inconsistent `new` or we do not want to report it.
76+
*/
77+
predicate whitelistedCallee(Function f) {
78+
// externs are special, so don't flag them
79+
f.inExternsFile() or
80+
// illegal constructor calls are flagged by query 'Illegal invocation',
81+
// so don't flag them
82+
f instanceof Constructor or
83+
// if `f` itself guards against missing `new`, don't flag it
84+
guardsAgainstMissingNew(f)
85+
}
86+
87+
/**
88+
* Holds if `call` should be whitelisted because it cannot cause problems
89+
* with inconsistent `new`.
90+
*/
91+
predicate whitelistedCall(DataFlow::CallNode call) {
92+
// super constructor calls behave more like `new`, so don't flag them
93+
call.asExpr() instanceof SuperCall or
94+
// don't flag if there is a receiver object
95+
exists(call.getReceiver())
96+
}
97+
98+
/**
99+
* Get the `new` or call (depending on whether `isNew` is true or false) of `f`
100+
* that comes first under a lexicographical ordering by file path, start line
101+
* and start column.
102+
*/
103+
DataFlow::InvokeNode getFirstInvocation(Function f, boolean isNew) {
104+
result = min(DataFlow::InvokeNode invk, string path, int line, int col |
105+
f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _) |
106+
invk order by path, line, col
107+
)
63108
}
64109

65110
from Function f, DataFlow::NewNode new, DataFlow::CallNode call
66-
where // externs are special, so don't flag them
67-
not f.inExternsFile() and
68-
// illegal constructor calls are flagged by query 'Illegal invocation',
69-
// so don't flag them
70-
not f instanceof Constructor and
71-
f = getALikelyCallee(new) and
72-
f = getALikelyCallee(call) and
73-
not guardsAgainstMissingNew(f) and
74-
not new.isUncertain() and
75-
not call.isUncertain() and
76-
// super constructor calls behave more like `new`, so don't flag them
77-
not call.asExpr() instanceof SuperCall and
78-
// don't flag if there is a receiver object
79-
not exists(call.getReceiver())
80-
select (FirstLineOf)f, capitalize(f.describe()) + " is invoked as a constructor here $@, " +
81-
"and as a normal function here $@.", new, new.toString(), call, call.toString()
111+
where new = getFirstInvocation(f, true) and
112+
call = getFirstInvocation(f, false)
113+
select (FirstLineOf)f, capitalize(f.describe()) + " is sometimes invoked as a constructor " +
114+
"(for example $@), and sometimes as a normal function (for example $@).",
115+
new, "here", call, "here"
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
| m.js:1:8:1:22 | functio ... = x;\\n} | Function A is invoked as a constructor here $@, and as a normal function here $@. | c1.js:2:1:2:9 | new A(42) | new A(42) | c2.js:2:1:2:5 | A(23) | A(23) |
2-
| tst.js:1:1:1:22 | functio ... = y;\\n} | Function Point is invoked as a constructor here $@, and as a normal function here $@. | tst.js:6:1:6:17 | new Point(23, 42) | new Point(23, 42) | tst.js:7:1:7:13 | Point(56, 72) | Point(56, 72) |
1+
| m.js:1:8:1:22 | functio ... = x;\\n} | Function A is sometimes invoked as a constructor (for example $@), and sometimes as a normal function (for example $@). | c1.js:2:1:2:9 | new A(42) | here | c2.js:2:1:2:5 | A(23) | here |
2+
| tst.js:1:1:1:22 | functio ... = y;\\n} | Function Point is sometimes invoked as a constructor (for example $@), and sometimes as a normal function (for example $@). | tst.js:6:1:6:17 | new Point(23, 42) | here | tst.js:7:1:7:13 | Point(56, 72) | here |

javascript/ql/test/query-tests/LanguageFeatures/InconsistentNew/tst.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ C(); // NOT OK, but flagged by IllegalInvocation
6363
new A(42);
6464
A.call({}, 23);
6565
})();
66+
67+
new Point(42, 23); // NOT OK, but not flagged since line 6 above was already flagged
68+
Point(56, 72); // NOT OK, but not flagged since line 7 above was already flagged

0 commit comments

Comments
 (0)