mirror of
https://github.com/github/codeql.git
synced 2025-12-20 10:46:30 +01:00
265 lines
7.9 KiB
Plaintext
265 lines
7.9 KiB
Plaintext
/**
|
|
* Provides classes and predicates for detecting files generated by popular module bundlers.
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Holds if `oe` looks like it was produced by [Browserify](http://browserify.org/).
|
|
*
|
|
* Generally, Browserify's output looks like this:
|
|
*
|
|
* ```javascript
|
|
* (function e(t, n, r) {
|
|
* // module loader code
|
|
* }({
|
|
* 1: [ function(require, module, exports) {
|
|
* require("./dep1");
|
|
* require("./dep2);
|
|
* // ...
|
|
* }, { "./dep1": 2, "./dep2": 4, ... } ],
|
|
* 2: [ function(require, module, exports) {
|
|
* // code for module "dep1"
|
|
* }, { ... } ],
|
|
* 3: ...,
|
|
* 4: [ function(require, module, exports) {
|
|
* // code for module "dep2"
|
|
* }, { ... } ],
|
|
* ...
|
|
* }, {}, [1]);
|
|
* ```
|
|
*/
|
|
predicate isBrowserifyBundle(ObjectExpr oe) {
|
|
// ensure that there is at least one property
|
|
forex(Property p | p = oe.getAProperty() |
|
|
// and each property looks like a packed module
|
|
isBrowserifyBundledModule(p)
|
|
) and
|
|
// the whole object must be passed to the module loader function
|
|
exists(CallExpr ce | ce.getCallee().getUnderlyingValue() instanceof Function |
|
|
// the module loader function always has three arguments
|
|
ce.getNumArgument() = 3 and
|
|
// the first of which is the bundle
|
|
ce.getArgument(0) = oe
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `e` could be a module name in a Browserify-created bundle, that is,
|
|
* it is either a decimal literal or a string literal.
|
|
*/
|
|
private predicate isBrowserifyModuleId(Expr e) {
|
|
e.(NumberLiteral).getValue().regexpMatch("[0-9]+") or
|
|
e instanceof ConstantString
|
|
}
|
|
|
|
/**
|
|
* Holds if `p` looks like a bundled module generated by Browserify.
|
|
*/
|
|
private predicate isBrowserifyBundledModule(Property p) {
|
|
// the key must be a module id
|
|
isBrowserifyModuleId(p.(ValueProperty).getNameExpr()) and
|
|
// the value must look like a bundled up module
|
|
exists(ArrayExpr ae | ae = p.getInit() |
|
|
// first element must be a function (known as the module factory function)
|
|
isBrowserifyModuleFactoryFunction(ae.getElement(0)) and
|
|
// second element must be an object literal listing dependencies
|
|
isBrowserifyDependencyMap(ae.getElement(1))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `factory` looks like a Browserify-generated module factory function.
|
|
*
|
|
* We check that each parameter is named one of `require`, `module` or `exports`,
|
|
* and that there is at least one of them. We also recognize `_dereq_` instead
|
|
* of `require` to account for additional mangling by
|
|
* [derequire](https://www.npmjs.com/package/derequire).
|
|
*
|
|
* Currently, Browserify always generates all three parameters, but this
|
|
* might well change in future, so we don't rely on it
|
|
*/
|
|
private predicate isBrowserifyModuleFactoryFunction(FunctionExpr factory) {
|
|
forex(Parameter parm | parm = factory.getAParameter() |
|
|
parm.getName().regexpMatch("require|module|exports|_dereq_")
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `deps` looks like a Browserify-generated dependency map for a bundled module.
|
|
*/
|
|
private predicate isBrowserifyDependencyMap(ObjectExpr deps) {
|
|
// there may be no dependencies, hence `forall` instead of `forex`
|
|
forall(Property dep | dep = deps.getAProperty() |
|
|
// each key must be a string literal
|
|
dep.(ValueProperty).getNameExpr() instanceof ConstantString and
|
|
// and each value must be a module id
|
|
isBrowserifyModuleId(dep.getInit())
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `m` is a function that looks like a bundled module created
|
|
* by Webpack.
|
|
*
|
|
* Parameters must be named either "module" or "exports",
|
|
* or their name must contain the substring "webpack_require"
|
|
* or "webpack_module_template_argument".
|
|
*/
|
|
private predicate isWebpackModule(Function m) {
|
|
forex(Parameter parm | parm = m.getAParameter() |
|
|
exists(string name | name = parm.getName() |
|
|
name.regexpMatch("module|exports|.*webpack_require.*|.*webpack_module_template_argument.*|.*unused_webpack_module.*")
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `ae` looks like it was produced by [Webpack](https://webpack.github.io/).
|
|
*
|
|
* Generally, Webpack's output looks like this:
|
|
*
|
|
* ```javascript
|
|
* (function(modules) {
|
|
* // module loader code
|
|
* })([
|
|
* function(module, exports, __webpack_require__) {
|
|
* __webpack_require(1);
|
|
* // ...
|
|
* },
|
|
* function(module, exports) {
|
|
* // does not use __webpack_require
|
|
* },
|
|
* // a module reference
|
|
* 1,
|
|
* // a module template instantiation
|
|
* [1, 2],
|
|
* ...
|
|
* ]);
|
|
* ```
|
|
*/
|
|
predicate isWebpackBundle(ArrayExpr ae) {
|
|
// ensure that there is at least one bundled module
|
|
isWebpackModule(ae.getAnElement().getUnderlyingValue()) and
|
|
// furthermore, every element is either
|
|
forall(Expr elt | elt = ae.getAnElement().getUnderlyingValue() |
|
|
// (1) a module
|
|
isWebpackModule(elt)
|
|
or
|
|
// (2) a module template instantiation
|
|
exists(ArrayExpr inner | inner = elt |
|
|
forex(Expr innerElt | innerElt = inner.getAnElement() | innerElt instanceof NumberLiteral)
|
|
)
|
|
or
|
|
// (3) a module reference
|
|
elt instanceof NumberLiteral
|
|
) and
|
|
// the whole array must be passed to a module loader function
|
|
exists(CallExpr ce | ce.getCallee().getUnderlyingValue() instanceof Function |
|
|
// which is the bundle
|
|
ce.getArgument(0) = ae
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `object` looks like a Webpack bundle of form:
|
|
* ```javascript
|
|
* var __webpack_modules__ = ({
|
|
* "file1": ((module, __webpack__exports__, __webpack_require__) => ...)
|
|
* ...
|
|
* })
|
|
* ```
|
|
*/
|
|
predicate isWebpackNamedBundle(ObjectExpr object) {
|
|
isWebpackModule(object.getAProperty().getInit().getUnderlyingValue()) and
|
|
exists(VarDef def |
|
|
def.getSource().(Expr).getUnderlyingValue() = object and
|
|
def.getTarget().(VarRef).getName() = "__webpack_modules__"
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `tl` is a collection of concatenated files by [atpackager](https://github.com/ariatemplates/atpackager).
|
|
*/
|
|
predicate isMultiPartBundle(TopLevel tl) {
|
|
exists(Comment c1, Comment c2 |
|
|
c1.getTopLevel() = tl and
|
|
c2.getTopLevel() = tl and
|
|
c1.getText() = "***MULTI-PART" and
|
|
c2.getText().regexpMatch("LOGICAL-PATH:.*")
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A comment that starts with '!'. Minifiers avoid removing such comments.
|
|
*/
|
|
class ExclamationPointComment extends Comment {
|
|
ExclamationPointComment() { this.getLine(0).matches("!%") }
|
|
}
|
|
|
|
/**
|
|
* Gets a comment that belongs to a run of consecutive `ExclamationPointComment`s starting with `head`.
|
|
*/
|
|
Comment getExclamationPointCommentInRun(ExclamationPointComment head) {
|
|
exists(File f |
|
|
exists(int n |
|
|
head.onLines(f, n, _) and
|
|
not exists(ExclamationPointComment d | d.onLines(f, _, n - 1))
|
|
) and
|
|
(
|
|
result = head
|
|
or
|
|
exists(ExclamationPointComment prev, int n |
|
|
prev = getExclamationPointCommentInRun(head) and
|
|
prev.onLines(f, _, n) and
|
|
result.onLines(f, n + 1, _)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if this is a bundle containing multiple licenses.
|
|
*/
|
|
predicate isMultiLicenseBundle(TopLevel tl) {
|
|
// case: comments preserved by minifiers
|
|
count(ExclamationPointComment head |
|
|
head.getTopLevel() = tl and
|
|
exists(ExclamationPointComment licenseIndicator |
|
|
licenseIndicator = getExclamationPointCommentInRun(head) and
|
|
licenseIndicator.getLine(_).regexpMatch("(?i).*\\b(copyright|license|\\d+\\.\\d+)\\b.*")
|
|
)
|
|
) > 1
|
|
or
|
|
// case: ordinary block comments lines that start with a license
|
|
count(BlockComment head |
|
|
head.getTopLevel() = tl and
|
|
head.getLine(_)
|
|
.regexpMatch("(?i)[\\s*]*(@license\\b.*|The [a-z0-9-]+ License (\\([a-z0-9-]+\\))?\\s*)")
|
|
) > 1
|
|
}
|
|
|
|
/**
|
|
* Holds if this is a bundle with a "bundle" directive.
|
|
*/
|
|
predicate isDirectiveBundle(TopLevel tl) {
|
|
exists(Directive::BundleDirective d | d.getTopLevel() = tl)
|
|
}
|
|
|
|
/**
|
|
* Holds if toplevel `tl` contains code that looks like the output of a module bundler.
|
|
*/
|
|
predicate isBundle(TopLevel tl) {
|
|
exists(Expr e | e.getTopLevel() = tl |
|
|
isBrowserifyBundle(e) or
|
|
isWebpackBundle(e) or
|
|
isWebpackNamedBundle(e)
|
|
)
|
|
or
|
|
isMultiPartBundle(tl)
|
|
or
|
|
isMultiLicenseBundle(tl)
|
|
or
|
|
isDirectiveBundle(tl)
|
|
}
|