mirror of
https://github.com/github/codeql.git
synced 2025-12-24 20:56:33 +01:00
104 lines
3.2 KiB
Plaintext
104 lines
3.2 KiB
Plaintext
/**
|
|
* @name Inconsistent use of 'new'
|
|
* @description If a function is intended to be a constructor, it should always
|
|
* be invoked with 'new'. Otherwise, it should always be invoked
|
|
* as a normal function, that is, without 'new'.
|
|
* @kind problem
|
|
* @problem.severity warning
|
|
* @id js/inconsistent-use-of-new
|
|
* @tags reliability
|
|
* correctness
|
|
* language-features
|
|
* @precision very-high
|
|
*/
|
|
|
|
import javascript
|
|
import semmle.javascript.RestrictedLocations
|
|
|
|
/**
|
|
* Holds if `f` contains code to guard against being invoked without `new`.
|
|
*
|
|
* There are many ways to implement such a check, but ultimately `f` will
|
|
* have to call itself using `new`, so that is what we look for.
|
|
*/
|
|
predicate guardsAgainstMissingNew(Function f) {
|
|
exists(DataFlow::NewNode new |
|
|
new.asExpr().getEnclosingFunction() = f and
|
|
f = new.getACallee()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a function that may be invoked at `cs`, preferring callees that
|
|
* are less likely to be derived due to analysis imprecision and excluding
|
|
* whitelisted call sites and callees. Additionally, `isNew` is bound to
|
|
* `true` if `cs` is a `new` expression, and to `false` otherwise.
|
|
*/
|
|
Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) {
|
|
result =
|
|
min(Function callee, int imprecision |
|
|
callee = cs.getACallee(imprecision)
|
|
|
|
|
callee order by imprecision
|
|
) and
|
|
not cs.isUncertain() and
|
|
not whitelistedCall(cs) and
|
|
not whitelistedCallee(result) and
|
|
(
|
|
cs instanceof DataFlow::NewNode and isNew = true
|
|
or
|
|
cs instanceof DataFlow::CallNode and isNew = false
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `f` should be whitelisted, either because it guards against
|
|
* inconsistent `new` or we do not want to report it.
|
|
*/
|
|
predicate whitelistedCallee(Function f) {
|
|
// externs are special, so don't flag them
|
|
f.inExternsFile()
|
|
or
|
|
// illegal constructor calls are flagged by query 'Illegal invocation',
|
|
// so don't flag them
|
|
f instanceof Constructor
|
|
or
|
|
// if `f` itself guards against missing `new`, don't flag it
|
|
guardsAgainstMissingNew(f)
|
|
}
|
|
|
|
/**
|
|
* Holds if `call` should be whitelisted because it cannot cause problems
|
|
* with inconsistent `new`.
|
|
*/
|
|
predicate whitelistedCall(DataFlow::CallNode call) {
|
|
// super constructor calls behave more like `new`, so don't flag them
|
|
call.asExpr() instanceof SuperCall
|
|
or
|
|
// don't flag if there is a receiver object
|
|
exists(call.getReceiver())
|
|
}
|
|
|
|
/**
|
|
* Get the `new` or call (depending on whether `isNew` is true or false) of `f`
|
|
* that comes first under a lexicographical ordering by file path, start line
|
|
* and start column.
|
|
*/
|
|
DataFlow::InvokeNode getFirstInvocation(Function f, boolean isNew) {
|
|
result =
|
|
min(DataFlow::InvokeNode invk, string path, int line, int col |
|
|
f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _)
|
|
|
|
|
invk order by path, line, col
|
|
)
|
|
}
|
|
|
|
from Function f, DataFlow::NewNode new, DataFlow::CallNode call
|
|
where
|
|
new = getFirstInvocation(f, true) and
|
|
call = getFirstInvocation(f, false)
|
|
select f.(FirstLineOf),
|
|
capitalize(f.describe()) + " is sometimes invoked as a constructor " +
|
|
"(for example $@), and sometimes as a normal function (for example $@).", new, "here", call,
|
|
"here"
|