mirror of
https://github.com/github/codeql.git
synced 2026-05-22 07:07:09 +02:00
Merge branch 'js/graph-export' into js/vea-hacking-models
This commit is contained in:
@@ -1,3 +1,21 @@
|
||||
## 0.8.13
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added support for TypeScript 5.4.
|
||||
|
||||
## 0.8.12
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.11
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.10
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.9
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Added support for TypeScript 5.4.
|
||||
3
javascript/ql/lib/change-notes/released/0.8.10.md
Normal file
3
javascript/ql/lib/change-notes/released/0.8.10.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.10
|
||||
|
||||
No user-facing changes.
|
||||
3
javascript/ql/lib/change-notes/released/0.8.11.md
Normal file
3
javascript/ql/lib/change-notes/released/0.8.11.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.11
|
||||
|
||||
No user-facing changes.
|
||||
3
javascript/ql/lib/change-notes/released/0.8.12.md
Normal file
3
javascript/ql/lib/change-notes/released/0.8.12.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.12
|
||||
|
||||
No user-facing changes.
|
||||
5
javascript/ql/lib/change-notes/released/0.8.13.md
Normal file
5
javascript/ql/lib/change-notes/released/0.8.13.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.8.13
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added support for TypeScript 5.4.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.8.9
|
||||
lastReleaseVersion: 0.8.13
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 0.8.10-dev
|
||||
version: 0.8.14-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
@@ -11,6 +11,7 @@ dependencies:
|
||||
codeql/regex: ${workspace}
|
||||
codeql/tutorial: ${workspace}
|
||||
codeql/util: ${workspace}
|
||||
codeql/xml: ${workspace}
|
||||
codeql/yaml: ${workspace}
|
||||
dataExtensions:
|
||||
- semmle/javascript/frameworks/**/model.yml
|
||||
|
||||
@@ -245,7 +245,7 @@ class TopLevel extends @toplevel, StmtContainer {
|
||||
/** Gets the number of lines containing comments in this toplevel. */
|
||||
int getNumberOfLinesOfComments() { numlines(this, _, _, result) }
|
||||
|
||||
override predicate isStrict() { this.getAStmt() instanceof StrictModeDecl }
|
||||
override predicate isStrict() { this.getAStmt() instanceof Directive::StrictModeDecl }
|
||||
|
||||
override ControlFlowNode getFirstControlFlowNode() { result = this.getEntry() }
|
||||
|
||||
|
||||
@@ -241,15 +241,23 @@ module API {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing an instance of this API component, that is, an object whose
|
||||
* constructor is the function represented by this node.
|
||||
* Gets a node representing an instance of the class represented by this node.
|
||||
* This includes instances of subclasses.
|
||||
*
|
||||
* For example, if this node represents a use of some class `A`, then there might be a node
|
||||
* representing instances of `A`, typically corresponding to expressions `new A()` at the
|
||||
* source level.
|
||||
* For example:
|
||||
* ```js
|
||||
* import { C } from "foo";
|
||||
*
|
||||
* This predicate may have multiple results when there are multiple constructor calls invoking this API component.
|
||||
* Consider using `getAnInstantiation()` if there is a need to distinguish between individual constructor calls.
|
||||
* new C(); // API::moduleImport("foo").getMember("C").getInstance()
|
||||
*
|
||||
* class D extends C {
|
||||
* m() {
|
||||
* this; // API::moduleImport("foo").getMember("C").getInstance()
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* new D(); // API::moduleImport("foo").getMember("C").getInstance()
|
||||
* ```
|
||||
*/
|
||||
cached
|
||||
Node getInstance() {
|
||||
@@ -493,16 +501,25 @@ module API {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location of this API node, if it corresponds to a program element with a source location.
|
||||
*/
|
||||
final Location getLocation() { result = this.getInducingNode().getLocation() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getLocation().hasLocationInfo()` instead.
|
||||
*
|
||||
* Holds if this node is located in file `path` between line `startline`, column `startcol`,
|
||||
* and line `endline`, column `endcol`.
|
||||
*
|
||||
* For nodes that do not have a meaningful location, `path` is the empty string and all other
|
||||
* parameters are zero.
|
||||
*/
|
||||
predicate hasLocationInfo(string path, int startline, int startcol, int endline, int endcol) {
|
||||
this.getInducingNode().hasLocationInfo(path, startline, startcol, endline, endcol)
|
||||
deprecated predicate hasLocationInfo(
|
||||
string path, int startline, int startcol, int endline, int endcol
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(path, startline, startcol, endline, endcol)
|
||||
or
|
||||
not exists(this.getInducingNode()) and
|
||||
not exists(this.getLocation()) and
|
||||
path = "" and
|
||||
startline = 0 and
|
||||
startcol = 0 and
|
||||
@@ -688,14 +705,7 @@ module API {
|
||||
or
|
||||
any(Type t).hasUnderlyingType(m, _)
|
||||
} or
|
||||
MkClassInstance(DataFlow::ClassNode cls) {
|
||||
hasSemantics(cls) and
|
||||
(
|
||||
cls = trackDefNode(_)
|
||||
or
|
||||
cls.getAnInstanceReference() = trackDefNode(_)
|
||||
)
|
||||
} or
|
||||
MkClassInstance(DataFlow::ClassNode cls) { needsDefNode(cls) } or
|
||||
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
|
||||
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
|
||||
/** A use of a TypeScript type. */
|
||||
@@ -708,6 +718,17 @@ module API {
|
||||
trackUseNode(src, true, bound, "").flowsTo(nd.getCalleeNode())
|
||||
}
|
||||
|
||||
private predicate needsDefNode(DataFlow::ClassNode cls) {
|
||||
hasSemantics(cls) and
|
||||
(
|
||||
cls = trackDefNode(_)
|
||||
or
|
||||
cls.getAnInstanceReference() = trackDefNode(_)
|
||||
or
|
||||
needsDefNode(cls.getADirectSubClass())
|
||||
)
|
||||
}
|
||||
|
||||
class TDef = MkModuleDef or TNonModuleDef;
|
||||
|
||||
class TNonModuleDef = MkModuleExport or MkClassInstance or MkDef or MkSyntheticCallbackArg;
|
||||
@@ -891,6 +912,17 @@ module API {
|
||||
(propDesc = Promises::errorProp() or propDesc = "")
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::ClassNode getALocalSubclass(DataFlow::SourceNode node) {
|
||||
result.getASuperClassNode().getALocalSource() = node
|
||||
}
|
||||
|
||||
bindingset[node]
|
||||
pragma[inline_late]
|
||||
private DataFlow::ClassNode getALocalSubclassFwd(DataFlow::SourceNode node) {
|
||||
result = getALocalSubclass(node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
|
||||
* `lbl` in the API graph.
|
||||
@@ -927,6 +959,15 @@ module API {
|
||||
or
|
||||
lbl = Label::forwardingFunction() and
|
||||
DataFlow::functionForwardingStep(pred.getALocalUse(), ref)
|
||||
or
|
||||
exists(DataFlow::ClassNode cls |
|
||||
lbl = Label::instance() and
|
||||
cls = getALocalSubclassFwd(pred).getADirectSubClass*()
|
||||
|
|
||||
ref = cls.getAReceiverNode()
|
||||
or
|
||||
ref = cls.getAClassReference().getAnInstantiation()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
|
||||
|
||||
@@ -237,7 +237,7 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
|
||||
|
||||
override predicate isStrict() {
|
||||
// check for explicit strict mode directive
|
||||
exists(StrictModeDecl smd | this = smd.getContainer()) or
|
||||
exists(Directive::StrictModeDecl smd | this = smd.getContainer()) or
|
||||
// check for enclosing strict function
|
||||
StmtContainer.super.isStrict() or
|
||||
// all parts of a class definition are strict code
|
||||
|
||||
@@ -259,149 +259,210 @@ class Directive extends MaybeDirective {
|
||||
}
|
||||
|
||||
/**
|
||||
* A known directive, such as a strict mode declaration.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use strict";
|
||||
* ```
|
||||
* Module containing subclasses of the `Directive` class.
|
||||
*/
|
||||
abstract class KnownDirective extends Directive { }
|
||||
module Directive {
|
||||
/**
|
||||
* A known directive, such as a strict mode declaration.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use strict";
|
||||
* ```
|
||||
*/
|
||||
abstract class KnownDirective extends Directive { }
|
||||
|
||||
/**
|
||||
* A strict mode declaration.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use strict";
|
||||
* ```
|
||||
*/
|
||||
class StrictModeDecl extends KnownDirective {
|
||||
StrictModeDecl() { this.getDirectiveText() = "use strict" }
|
||||
}
|
||||
/**
|
||||
* A strict mode declaration.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use strict";
|
||||
* ```
|
||||
*/
|
||||
class StrictModeDecl extends KnownDirective {
|
||||
StrictModeDecl() { this.getDirectiveText() = "use strict" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An asm.js directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use asm";
|
||||
* ```
|
||||
*/
|
||||
class AsmJSDirective extends KnownDirective {
|
||||
AsmJSDirective() { this.getDirectiveText() = "use asm" }
|
||||
}
|
||||
/**
|
||||
* An asm.js directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use asm";
|
||||
* ```
|
||||
*/
|
||||
class AsmJSDirective extends KnownDirective {
|
||||
AsmJSDirective() { this.getDirectiveText() = "use asm" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Babel directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use babel";
|
||||
* ```
|
||||
*/
|
||||
class BabelDirective extends KnownDirective {
|
||||
BabelDirective() { this.getDirectiveText() = "use babel" }
|
||||
}
|
||||
/**
|
||||
* A Babel directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use babel";
|
||||
* ```
|
||||
*/
|
||||
class BabelDirective extends KnownDirective {
|
||||
BabelDirective() { this.getDirectiveText() = "use babel" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A legacy 6to5 directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use 6to5";
|
||||
* ```
|
||||
*/
|
||||
class SixToFiveDirective extends KnownDirective {
|
||||
SixToFiveDirective() { this.getDirectiveText() = "use 6to5" }
|
||||
}
|
||||
/**
|
||||
* A legacy 6to5 directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use 6to5";
|
||||
* ```
|
||||
*/
|
||||
class SixToFiveDirective extends KnownDirective {
|
||||
SixToFiveDirective() { this.getDirectiveText() = "use 6to5" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A SystemJS `format` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "format global";
|
||||
* ```
|
||||
*/
|
||||
class SystemJSFormatDirective extends KnownDirective {
|
||||
SystemJSFormatDirective() {
|
||||
this.getDirectiveText().regexpMatch("format (cjs|esm|global|register)")
|
||||
/**
|
||||
* A SystemJS `format` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "format global";
|
||||
* ```
|
||||
*/
|
||||
class SystemJSFormatDirective extends KnownDirective {
|
||||
SystemJSFormatDirective() {
|
||||
this.getDirectiveText().regexpMatch("format (cjs|esm|global|register)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SystemJS `format register` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "format register";
|
||||
* ```
|
||||
*/
|
||||
class FormatRegisterDirective extends SystemJSFormatDirective {
|
||||
FormatRegisterDirective() { this.getDirectiveText() = "format register" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ngInject` or `ngNoInject` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "ngInject";
|
||||
* ```
|
||||
*/
|
||||
class NgInjectDirective extends KnownDirective {
|
||||
NgInjectDirective() { this.getDirectiveText().regexpMatch("ng(No)?Inject") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A YUI compressor directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "console:nomunge";
|
||||
* ```
|
||||
*/
|
||||
class YuiDirective extends KnownDirective {
|
||||
YuiDirective() {
|
||||
this.getDirectiveText().regexpMatch("([a-z0-9_]+:nomunge, ?)*([a-z0-9_]+:nomunge)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SystemJS `deps` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "deps fs";
|
||||
* ```
|
||||
*/
|
||||
class SystemJSDepsDirective extends KnownDirective {
|
||||
SystemJSDepsDirective() { this.getDirectiveText().regexpMatch("deps [^ ]+") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `bundle` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "bundle";
|
||||
* ```
|
||||
*/
|
||||
class BundleDirective extends KnownDirective {
|
||||
BundleDirective() { this.getDirectiveText() = "bundle" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `use server` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use server";
|
||||
* ```
|
||||
*/
|
||||
class UseServerDirective extends KnownDirective {
|
||||
UseServerDirective() { this.getDirectiveText() = "use server" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `use client` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "use client";
|
||||
* ```
|
||||
*/
|
||||
class UseClientDirective extends KnownDirective {
|
||||
UseClientDirective() { this.getDirectiveText() = "use client" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SystemJS `format register` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "format register";
|
||||
* ```
|
||||
*/
|
||||
class FormatRegisterDirective extends SystemJSFormatDirective {
|
||||
FormatRegisterDirective() { this.getDirectiveText() = "format register" }
|
||||
}
|
||||
/** DEPRECATED. Use `Directive::KnownDirective` instead. */
|
||||
deprecated class KnownDirective = Directive::KnownDirective;
|
||||
|
||||
/**
|
||||
* A `ngInject` or `ngNoInject` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "ngInject";
|
||||
* ```
|
||||
*/
|
||||
class NgInjectDirective extends KnownDirective {
|
||||
NgInjectDirective() { this.getDirectiveText().regexpMatch("ng(No)?Inject") }
|
||||
}
|
||||
/** DEPRECATED. Use `Directive::StrictModeDecl` instead. */
|
||||
deprecated class StrictModeDecl = Directive::StrictModeDecl;
|
||||
|
||||
/**
|
||||
* A YUI compressor directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "console:nomunge";
|
||||
* ```
|
||||
*/
|
||||
class YuiDirective extends KnownDirective {
|
||||
YuiDirective() {
|
||||
this.getDirectiveText().regexpMatch("([a-z0-9_]+:nomunge, ?)*([a-z0-9_]+:nomunge)")
|
||||
}
|
||||
}
|
||||
/** DEPRECATED. Use `Directive::AsmJSDirective` instead. */
|
||||
deprecated class AsmJSDirective = Directive::AsmJSDirective;
|
||||
|
||||
/**
|
||||
* A SystemJS `deps` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "deps fs";
|
||||
* ```
|
||||
*/
|
||||
class SystemJSDepsDirective extends KnownDirective {
|
||||
SystemJSDepsDirective() { this.getDirectiveText().regexpMatch("deps [^ ]+") }
|
||||
}
|
||||
/** DEPRECATED. Use `Directive::BabelDirective` instead. */
|
||||
deprecated class BabelDirective = Directive::BabelDirective;
|
||||
|
||||
/**
|
||||
* A `bundle` directive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* "bundle";
|
||||
* ```
|
||||
*/
|
||||
class BundleDirective extends KnownDirective {
|
||||
BundleDirective() { this.getDirectiveText() = "bundle" }
|
||||
}
|
||||
/** DEPRECATED. Use `Directive::SixToFiveDirective` instead. */
|
||||
deprecated class SixToFiveDirective = Directive::SixToFiveDirective;
|
||||
|
||||
/** DEPRECATED. Use `Directive::SystemJSFormatDirective` instead. */
|
||||
deprecated class SystemJSFormatDirective = Directive::SystemJSFormatDirective;
|
||||
|
||||
/** DEPRECATED. Use `Directive::NgInjectDirective` instead. */
|
||||
deprecated class NgInjectDirective = Directive::NgInjectDirective;
|
||||
|
||||
/** DEPRECATED. Use `Directive::YuiDirective` instead. */
|
||||
deprecated class YuiDirective = Directive::YuiDirective;
|
||||
|
||||
/** DEPRECATED. Use `Directive::SystemJSDepsDirective` instead. */
|
||||
deprecated class SystemJSDepsDirective = Directive::SystemJSDepsDirective;
|
||||
|
||||
/** DEPRECATED. Use `Directive::BundleDirective` instead. */
|
||||
deprecated class BundleDirective = Directive::BundleDirective;
|
||||
|
||||
/**
|
||||
* An `if` statement.
|
||||
|
||||
@@ -4,302 +4,67 @@
|
||||
|
||||
import semmle.files.FileSystem
|
||||
private import semmle.javascript.internal.Locations
|
||||
private import codeql.xml.Xml
|
||||
|
||||
private class TXmlLocatable =
|
||||
@xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters;
|
||||
private module Input implements InputSig<File, DbLocation> {
|
||||
class XmlLocatableBase = @xmllocatable or @xmlnamespaceable;
|
||||
|
||||
/** An XML element that has a location. */
|
||||
class XmlLocatable extends @xmllocatable, TXmlLocatable {
|
||||
/** Gets the source location for this element. */
|
||||
DbLocation getLocation() { result = getLocatableLocation(this) }
|
||||
predicate xmllocations_(XmlLocatableBase e, DbLocation loc) { loc = getLocatableLocation(e) }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
class XmlParentBase = @xmlparent;
|
||||
|
||||
class XmlNamespaceableBase = @xmlnamespaceable;
|
||||
|
||||
class XmlElementBase = @xmlelement;
|
||||
|
||||
class XmlFileBase = File;
|
||||
|
||||
predicate xmlEncoding_(XmlFileBase f, string enc) { xmlEncoding(f, enc) }
|
||||
|
||||
class XmlDtdBase = @xmldtd;
|
||||
|
||||
predicate xmlDTDs_(XmlDtdBase e, string root, string publicId, string systemId, XmlFileBase file) {
|
||||
xmlDTDs(e, root, publicId, systemId, file)
|
||||
}
|
||||
|
||||
predicate xmlElements_(
|
||||
XmlElementBase e, string name, XmlParentBase parent, int idx, XmlFileBase file
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
xmlElements(e, name, parent, idx, file)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { none() } // overridden in subclasses
|
||||
}
|
||||
class XmlAttributeBase = @xmlattribute;
|
||||
|
||||
/**
|
||||
* An `XmlParent` is either an `XmlElement` or an `XmlFile`,
|
||||
* both of which can contain other elements.
|
||||
*/
|
||||
class XmlParent extends @xmlparent {
|
||||
XmlParent() {
|
||||
// explicitly restrict `this` to be either an `XmlElement` or an `XmlFile`;
|
||||
// the type `@xmlparent` currently also includes non-XML files
|
||||
this instanceof @xmlelement or xmlEncoding(this, _)
|
||||
predicate xmlAttrs_(
|
||||
XmlAttributeBase e, XmlElementBase elementid, string name, string value, int idx,
|
||||
XmlFileBase file
|
||||
) {
|
||||
xmlAttrs(e, elementid, name, value, idx, file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a printable representation of this XML parent.
|
||||
* (Intended to be overridden in subclasses.)
|
||||
*/
|
||||
string getName() { none() } // overridden in subclasses
|
||||
class XmlNamespaceBase = @xmlnamespace;
|
||||
|
||||
/** Gets the file to which this XML parent belongs. */
|
||||
XmlFile getFile() { result = this or xmlElements(this, _, _, _, result) }
|
||||
|
||||
/** Gets the child element at a specified index of this XML parent. */
|
||||
XmlElement getChild(int index) { xmlElements(result, _, this, index, _) }
|
||||
|
||||
/** Gets a child element of this XML parent. */
|
||||
XmlElement getAChild() { xmlElements(result, _, this, _, _) }
|
||||
|
||||
/** Gets a child element of this XML parent with the given `name`. */
|
||||
XmlElement getAChild(string name) { xmlElements(result, _, this, _, _) and result.hasName(name) }
|
||||
|
||||
/** Gets a comment that is a child of this XML parent. */
|
||||
XmlComment getAComment() { xmlComments(result, _, this, _) }
|
||||
|
||||
/** Gets a character sequence that is a child of this XML parent. */
|
||||
XmlCharacters getACharactersSet() { xmlChars(result, _, this, _, _, _) }
|
||||
|
||||
/** Gets the depth in the tree. (Overridden in XmlElement.) */
|
||||
int getDepth() { result = 0 }
|
||||
|
||||
/** Gets the number of child XML elements of this XML parent. */
|
||||
int getNumberOfChildren() { result = count(XmlElement e | xmlElements(e, _, this, _, _)) }
|
||||
|
||||
/** Gets the number of places in the body of this XML parent where text occurs. */
|
||||
int getNumberOfCharacterSets() { result = count(int pos | xmlChars(_, _, this, pos, _, _)) }
|
||||
|
||||
/**
|
||||
* Gets the result of appending all the character sequences of this XML parent from
|
||||
* left to right, separated by a space.
|
||||
*/
|
||||
string allCharactersString() {
|
||||
result =
|
||||
concat(string chars, int pos | xmlChars(_, chars, this, pos, _, _) | chars, " " order by pos)
|
||||
predicate xmlNs_(XmlNamespaceBase e, string prefixName, string uri, XmlFileBase file) {
|
||||
xmlNs(e, prefixName, uri, file)
|
||||
}
|
||||
|
||||
/** Gets the text value contained in this XML parent. */
|
||||
string getTextValue() { result = this.allCharactersString() }
|
||||
predicate xmlHasNs_(XmlNamespaceableBase e, XmlNamespaceBase ns, XmlFileBase file) {
|
||||
xmlHasNs(e, ns, file)
|
||||
}
|
||||
|
||||
/** Gets a printable representation of this XML parent. */
|
||||
string toString() { result = this.getName() }
|
||||
}
|
||||
class XmlCommentBase = @xmlcomment;
|
||||
|
||||
/** An XML file. */
|
||||
class XmlFile extends XmlParent, File {
|
||||
XmlFile() { xmlEncoding(this, _) }
|
||||
predicate xmlComments_(XmlCommentBase e, string text, XmlParentBase parent, XmlFileBase file) {
|
||||
xmlComments(e, text, parent, file)
|
||||
}
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = this.getName() }
|
||||
class XmlCharactersBase = @xmlcharacters;
|
||||
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
|
||||
/** Gets the encoding of this XML file. */
|
||||
string getEncoding() { xmlEncoding(this, result) }
|
||||
|
||||
/** Gets the XML file itself. */
|
||||
override XmlFile getFile() { result = this }
|
||||
|
||||
/** Gets a top-most element in an XML file. */
|
||||
XmlElement getARootElement() { result = this.getAChild() }
|
||||
|
||||
/** Gets a DTD associated with this XML file. */
|
||||
XmlDtd getADtd() { xmlDTDs(result, _, _, _, this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An XML document type definition (DTD).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* <!ELEMENT person (firstName, lastName?)>
|
||||
* <!ELEMENT firstName (#PCDATA)>
|
||||
* <!ELEMENT lastName (#PCDATA)>
|
||||
* ```
|
||||
*/
|
||||
class XmlDtd extends XmlLocatable, @xmldtd {
|
||||
/** Gets the name of the root element of this DTD. */
|
||||
string getRoot() { xmlDTDs(this, result, _, _, _) }
|
||||
|
||||
/** Gets the public ID of this DTD. */
|
||||
string getPublicId() { xmlDTDs(this, _, result, _, _) }
|
||||
|
||||
/** Gets the system ID of this DTD. */
|
||||
string getSystemId() { xmlDTDs(this, _, _, result, _) }
|
||||
|
||||
/** Holds if this DTD is public. */
|
||||
predicate isPublic() { not xmlDTDs(this, _, "", _, _) }
|
||||
|
||||
/** Gets the parent of this DTD. */
|
||||
XmlParent getParent() { xmlDTDs(this, _, _, _, result) }
|
||||
|
||||
override string toString() {
|
||||
this.isPublic() and
|
||||
result = this.getRoot() + " PUBLIC '" + this.getPublicId() + "' '" + this.getSystemId() + "'"
|
||||
or
|
||||
not this.isPublic() and
|
||||
result = this.getRoot() + " SYSTEM '" + this.getSystemId() + "'"
|
||||
predicate xmlChars_(
|
||||
XmlCharactersBase e, string text, XmlParentBase parent, int idx, int isCDATA, XmlFileBase file
|
||||
) {
|
||||
xmlChars(e, text, parent, idx, isCDATA, file)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An XML element in an XML file.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* <manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
* package="com.example.exampleapp" android:versionCode="1">
|
||||
* </manifest>
|
||||
* ```
|
||||
*/
|
||||
class XmlElement extends @xmlelement, XmlParent, XmlLocatable {
|
||||
/** Holds if this XML element has the given `name`. */
|
||||
predicate hasName(string name) { name = this.getName() }
|
||||
|
||||
/** Gets the name of this XML element. */
|
||||
override string getName() { xmlElements(this, result, _, _, _) }
|
||||
|
||||
/** Gets the XML file in which this XML element occurs. */
|
||||
override XmlFile getFile() { xmlElements(this, _, _, _, result) }
|
||||
|
||||
/** Gets the parent of this XML element. */
|
||||
XmlParent getParent() { xmlElements(this, _, result, _, _) }
|
||||
|
||||
/** Gets the index of this XML element among its parent's children. */
|
||||
int getIndex() { xmlElements(this, _, _, result, _) }
|
||||
|
||||
/** Holds if this XML element has a namespace. */
|
||||
predicate hasNamespace() { xmlHasNs(this, _, _) }
|
||||
|
||||
/** Gets the namespace of this XML element, if any. */
|
||||
XmlNamespace getNamespace() { xmlHasNs(this, result, _) }
|
||||
|
||||
/** Gets the index of this XML element among its parent's children. */
|
||||
int getElementPositionIndex() { xmlElements(this, _, _, result, _) }
|
||||
|
||||
/** Gets the depth of this element within the XML file tree structure. */
|
||||
override int getDepth() { result = this.getParent().getDepth() + 1 }
|
||||
|
||||
/** Gets an XML attribute of this XML element. */
|
||||
XmlAttribute getAnAttribute() { result.getElement() = this }
|
||||
|
||||
/** Gets the attribute with the specified `name`, if any. */
|
||||
XmlAttribute getAttribute(string name) { result.getElement() = this and result.getName() = name }
|
||||
|
||||
/** Holds if this XML element has an attribute with the specified `name`. */
|
||||
predicate hasAttribute(string name) { exists(this.getAttribute(name)) }
|
||||
|
||||
/** Gets the value of the attribute with the specified `name`, if any. */
|
||||
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
|
||||
|
||||
/** Gets a printable representation of this XML element. */
|
||||
override string toString() { result = this.getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An attribute that occurs inside an XML element.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* package="com.example.exampleapp"
|
||||
* android:versionCode="1"
|
||||
* ```
|
||||
*/
|
||||
class XmlAttribute extends @xmlattribute, XmlLocatable {
|
||||
/** Gets the name of this attribute. */
|
||||
string getName() { xmlAttrs(this, _, result, _, _, _) }
|
||||
|
||||
/** Gets the XML element to which this attribute belongs. */
|
||||
XmlElement getElement() { xmlAttrs(this, result, _, _, _, _) }
|
||||
|
||||
/** Holds if this attribute has a namespace. */
|
||||
predicate hasNamespace() { xmlHasNs(this, _, _) }
|
||||
|
||||
/** Gets the namespace of this attribute, if any. */
|
||||
XmlNamespace getNamespace() { xmlHasNs(this, result, _) }
|
||||
|
||||
/** Gets the value of this attribute. */
|
||||
string getValue() { xmlAttrs(this, _, _, result, _, _) }
|
||||
|
||||
/** Gets a printable representation of this XML attribute. */
|
||||
override string toString() { result = this.getName() + "=" + this.getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A namespace used in an XML file.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
* ```
|
||||
*/
|
||||
class XmlNamespace extends XmlLocatable, @xmlnamespace {
|
||||
/** Gets the prefix of this namespace. */
|
||||
string getPrefix() { xmlNs(this, result, _, _) }
|
||||
|
||||
/** Gets the URI of this namespace. */
|
||||
string getUri() { xmlNs(this, _, result, _) }
|
||||
|
||||
/** Holds if this namespace has no prefix. */
|
||||
predicate isDefault() { this.getPrefix() = "" }
|
||||
|
||||
override string toString() {
|
||||
this.isDefault() and result = this.getUri()
|
||||
or
|
||||
not this.isDefault() and result = this.getPrefix() + ":" + this.getUri()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment in an XML file.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* <!-- This is a comment. -->
|
||||
* ```
|
||||
*/
|
||||
class XmlComment extends @xmlcomment, XmlLocatable {
|
||||
/** Gets the text content of this XML comment. */
|
||||
string getText() { xmlComments(this, result, _, _) }
|
||||
|
||||
/** Gets the parent of this XML comment. */
|
||||
XmlParent getParent() { xmlComments(this, _, result, _) }
|
||||
|
||||
/** Gets a printable representation of this XML comment. */
|
||||
override string toString() { result = this.getText() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sequence of characters that occurs between opening and
|
||||
* closing tags of an XML element, excluding other elements.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* <content>This is a sequence of characters.</content>
|
||||
* ```
|
||||
*/
|
||||
class XmlCharacters extends @xmlcharacters, XmlLocatable {
|
||||
/** Gets the content of this character sequence. */
|
||||
string getCharacters() { xmlChars(this, result, _, _, _, _) }
|
||||
|
||||
/** Gets the parent of this character sequence. */
|
||||
XmlParent getParent() { xmlChars(this, _, result, _, _, _) }
|
||||
|
||||
/** Holds if this character sequence is CDATA. */
|
||||
predicate isCDATA() { xmlChars(this, _, _, _, 1, _) }
|
||||
|
||||
/** Gets a printable representation of this XML character sequence. */
|
||||
override string toString() { result = this.getCharacters() }
|
||||
}
|
||||
import Make<File, DbLocation, Input>
|
||||
|
||||
@@ -151,7 +151,11 @@ private predicate isPrivateAssignment(DataFlow::Node node) {
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isPrivateLike(API::Node node) { isPrivateAssignment(node.asSink()) }
|
||||
/**
|
||||
* Holds if `node` is the sink node corresponding to the right-hand side of a private declaration,
|
||||
* like a private field (`#field`) or class member with the `private` modifier.
|
||||
*/
|
||||
predicate isPrivateLike(API::Node node) { isPrivateAssignment(node.asSink()) }
|
||||
|
||||
bindingset[name]
|
||||
private int getNameBadness(string name) {
|
||||
|
||||
@@ -242,7 +242,9 @@ predicate isMultiLicenseBundle(TopLevel tl) {
|
||||
/**
|
||||
* Holds if this is a bundle with a "bundle" directive.
|
||||
*/
|
||||
predicate isDirectiveBundle(TopLevel tl) { exists(BundleDirective d | d.getTopLevel() = tl) }
|
||||
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.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import codeql.serverless.ServerLess
|
||||
private import codeql.serverless.ServerLess
|
||||
|
||||
private module YamlImpl implements Input {
|
||||
import semmle.javascript.Files
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
private import javascript
|
||||
private import internal.ApiGraphModels as Shared
|
||||
private import internal.ApiGraphModelsSpecific as Specific
|
||||
private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
|
||||
import Shared::ModelInput as ModelInput
|
||||
import Shared::ModelOutput as ModelOutput
|
||||
|
||||
@@ -55,3 +56,106 @@ private class TaintStepFromSummary extends TaintTracking::SharedTaintStep {
|
||||
summaryStepNodes(pred, succ, "taint")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies which parts of the API graph to export in `ModelExport`.
|
||||
*/
|
||||
signature module ModelExportSig {
|
||||
/**
|
||||
* Holds if the exported model should contain `node`, if it is publicly accessible.
|
||||
*
|
||||
* This ensures that all ways to access `node` will be exported in type models.
|
||||
*/
|
||||
predicate shouldContain(API::Node node);
|
||||
|
||||
/**
|
||||
* Holds if a named must be generated for `node` if it is to be included in the exported graph.
|
||||
*/
|
||||
default predicate mustBeNamed(API::Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the exported model should preserve all paths leading to an instance of `type`,
|
||||
* including partial ones. It does not need to be closed transitively, `ModelExport` will
|
||||
* extend this to include type models from which `type` can be derived.
|
||||
*/
|
||||
default predicate shouldContainType(string type) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Module for exporting type models for a given set of nodes in the API graph.
|
||||
*/
|
||||
module ModelExport<ModelExportSig S> {
|
||||
private import codeql.mad.dynamic.GraphExport
|
||||
private import internal.ApiGraphModelsExport
|
||||
|
||||
private module GraphExportConfig implements GraphExportSig<Location, API::Node> {
|
||||
predicate edge = Specific::apiGraphHasEdge/3;
|
||||
|
||||
predicate shouldContain = S::shouldContain/1;
|
||||
|
||||
predicate shouldNotContain(API::Node node) {
|
||||
EndpointNaming::isPrivateLike(node)
|
||||
or
|
||||
node instanceof API::Use
|
||||
}
|
||||
|
||||
predicate mustBeNamed(API::Node node) {
|
||||
node.getAValueReachingSink() instanceof DataFlow::ClassNode
|
||||
or
|
||||
node = API::Internal::getClassInstance(_)
|
||||
or
|
||||
S::mustBeNamed(node)
|
||||
}
|
||||
|
||||
predicate exposedName(API::Node node, string type, string path) {
|
||||
node = API::moduleExport(type) and path = ""
|
||||
}
|
||||
|
||||
predicate suggestedName(API::Node node, string type) {
|
||||
exists(string package, string name |
|
||||
(
|
||||
EndpointNaming::sinkHasPrimaryName(node, package, name) and
|
||||
not EndpointNaming::aliasDefinition(_, _, _, _, node)
|
||||
or
|
||||
EndpointNaming::aliasDefinition(_, _, package, name, node)
|
||||
) and
|
||||
type = EndpointNaming::renderName(package, name)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[host]
|
||||
predicate hasTypeSummary(API::Node host, string path) {
|
||||
exists(string methodName |
|
||||
functionReturnsReceiver(host.getMember(methodName).getAValueReachingSink()) and
|
||||
path = "Member[" + methodName + "].ReturnValue"
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate functionReturnsReceiver(DataFlow::FunctionNode func) {
|
||||
getAReceiverRef(func).flowsTo(func.getReturnNode())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::MethodCallNode getAReceiverCall(DataFlow::FunctionNode func) {
|
||||
result = getAReceiverRef(func).getAMethodCall()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callReturnsReceiver(DataFlow::MethodCallNode call) {
|
||||
functionReturnsReceiver(call.getACallee().flow())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::SourceNode getAReceiverRef(DataFlow::FunctionNode func) {
|
||||
result = func.getReceiver()
|
||||
or
|
||||
result = getAReceiverCall(func) and
|
||||
callReturnsReceiver(result)
|
||||
}
|
||||
}
|
||||
|
||||
private module ExportedGraph = TypeGraphExport<GraphExportConfig, S::shouldContainType/1>;
|
||||
|
||||
import ExportedGraph
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ private predicate summaryModel(string type, string path, string input, string ou
|
||||
}
|
||||
|
||||
/** Holds if a type model exists for the given parameters. */
|
||||
private predicate typeModel(string type1, string type2, string path) {
|
||||
predicate typeModel(string type1, string type2, string path) {
|
||||
exists(string row |
|
||||
typeModel(row) and
|
||||
row.splitAt(";", 0) = type1 and
|
||||
@@ -435,7 +435,7 @@ private API::Node getNodeFromType(string type) {
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(type, path)` tuple.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromPath(string type, AccessPath path, int n) {
|
||||
API::Node getNodeFromPath(string type, AccessPath path, int n) {
|
||||
isRelevantFullPath(type, path) and
|
||||
(
|
||||
n = 0 and
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Contains an extension of `GraphExport` that relies on API graph specific functionality.
|
||||
*/
|
||||
|
||||
private import ApiGraphModels as Shared
|
||||
private import codeql.mad.dynamic.GraphExport
|
||||
private import ApiGraphModelsSpecific as Specific
|
||||
|
||||
private module API = Specific::API;
|
||||
|
||||
private import Shared
|
||||
|
||||
/**
|
||||
* Holds if some proper prefix of `(type, path)` evaluated to `node`, where `remainingPath`
|
||||
* is bound to the suffix of `path` that was not evaluated yet.
|
||||
*/
|
||||
bindingset[type, path]
|
||||
predicate partiallyEvaluatedModel(string type, string path, API::Node node, string remainingPath) {
|
||||
exists(int n, AccessPath accessPath |
|
||||
accessPath = path and
|
||||
getNodeFromPath(type, accessPath, n) = node and
|
||||
n > 0 and
|
||||
// Note that `n < accessPath.getNumToken()` is implied by the use of strictconcat()
|
||||
remainingPath =
|
||||
strictconcat(int k |
|
||||
k = [n .. accessPath.getNumToken() - 1]
|
||||
|
|
||||
accessPath.getToken(k), "." order by k
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `type` and all types leading to `type` should be re-exported.
|
||||
*/
|
||||
signature predicate shouldContainTypeSig(string type);
|
||||
|
||||
/**
|
||||
* Wrapper around `GraphExport` that also exports information about re-exported types.
|
||||
*
|
||||
* ### JavaScript example 1
|
||||
* For example, suppose `shouldContainType("foo")` holds, and the following is the entry point for a package `bar`:
|
||||
* ```js
|
||||
* // bar.js
|
||||
* module.exports.xxx = require('foo');
|
||||
* ```
|
||||
* then this would generate the following type model:
|
||||
* ```
|
||||
* foo; bar; Member[xxx]
|
||||
* ```
|
||||
*
|
||||
* ### JavaScript example 2
|
||||
* For a more complex case, suppose the following type model exists:
|
||||
* ```
|
||||
* foo.XYZ; foo; Member[x].Member[y].Member[z]
|
||||
* ```
|
||||
* And the package exports something that matches a prefix of the access path above:
|
||||
* ```js
|
||||
* module.exports.blah = require('foo').x.y;
|
||||
* ```
|
||||
* This would result in the following type model:
|
||||
* ```
|
||||
* foo.XYZ; bar; Member[blah].Member[z]
|
||||
* ```
|
||||
* Notice that the access path `Member[blah].Member[z]` consists of an access path generated from the API
|
||||
* graph, with pieces of the access path from the original type model appended to it.
|
||||
*/
|
||||
module TypeGraphExport<
|
||||
GraphExportSig<Specific::Location, API::Node> S, shouldContainTypeSig/1 shouldContainType>
|
||||
{
|
||||
/** Like `shouldContainType` but includes types that lead to `type` via type models. */
|
||||
private predicate shouldContainTypeEx(string type) {
|
||||
shouldContainType(type)
|
||||
or
|
||||
exists(string prevType |
|
||||
shouldContainType(prevType) and
|
||||
Shared::typeModel(prevType, type, _)
|
||||
)
|
||||
}
|
||||
|
||||
private module Config implements GraphExportSig<Specific::Location, API::Node> {
|
||||
import S
|
||||
|
||||
predicate shouldContain(API::Node node) {
|
||||
S::shouldContain(node)
|
||||
or
|
||||
exists(string type1 | shouldContainTypeEx(type1) |
|
||||
ModelOutput::getATypeNode(type1).getAValueReachableFromSource() = node.asSink()
|
||||
or
|
||||
exists(string type2, string path |
|
||||
Shared::typeModel(type1, type2, path) and
|
||||
getNodeFromPath(type2, path, _).getAValueReachableFromSource() = node.asSink()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module ExportedGraph = GraphExport<Specific::Location, API::Node, Config>;
|
||||
|
||||
import ExportedGraph
|
||||
|
||||
/**
|
||||
* Holds if `type1, type2, path` should be emitted as a type model, that is `(type2, path)` leads to an instance of `type1`.
|
||||
*/
|
||||
predicate typeModel(string type1, string type2, string path) {
|
||||
ExportedGraph::typeModel(type1, type2, path)
|
||||
or
|
||||
shouldContainTypeEx(type1) and
|
||||
exists(API::Node node |
|
||||
// A relevant type is exported directly
|
||||
Specific::sourceFlowsToSink(ModelOutput::getATypeNode(type1), node) and
|
||||
ExportedGraph::pathToNode(type2, path, node)
|
||||
or
|
||||
// Something that leads to a relevant type, but didn't finish its access path, is exported
|
||||
exists(string midType, string midPath, string remainingPath, string prefix, API::Node source |
|
||||
Shared::typeModel(type1, midType, midPath) and
|
||||
partiallyEvaluatedModel(midType, midPath, source, remainingPath) and
|
||||
Specific::sourceFlowsToSink(source, node) and
|
||||
ExportedGraph::pathToNode(type2, prefix, node) and
|
||||
path = join(prefix, remainingPath)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ module API = JS::API;
|
||||
|
||||
import JS::DataFlow as DataFlow
|
||||
|
||||
class Location = JS::Location;
|
||||
|
||||
/**
|
||||
* Holds if `rawType` represents the JavaScript type `qualifiedName` from the given NPM `package`.
|
||||
*
|
||||
@@ -353,3 +355,54 @@ module ModelOutputSpecific {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the edge `pred -> succ` labelled with `path` exists in the API graph.
|
||||
*/
|
||||
bindingset[pred]
|
||||
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
|
||||
exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]")
|
||||
or
|
||||
succ = pred.getUnknownMember() and path = "AnyMember"
|
||||
or
|
||||
succ = pred.getInstance() and path = "Instance"
|
||||
or
|
||||
succ = pred.getReturn() and path = "ReturnValue"
|
||||
or
|
||||
exists(int n | succ = pred.getParameter(n) |
|
||||
if pred instanceof API::Use then path = "Argument[" + n + "]" else path = "Parameter[" + n + "]"
|
||||
)
|
||||
or
|
||||
succ = pred.getPromised() and path = "Awaited"
|
||||
or
|
||||
exists(DataFlow::ClassNode cls |
|
||||
pred = API::Internal::getClassInstance(cls.getADirectSubClass()) and
|
||||
succ = API::Internal::getClassInstance(cls) and
|
||||
path = ""
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the value of `source` is exposed at `sink`.
|
||||
*/
|
||||
bindingset[source]
|
||||
predicate sourceFlowsToSink(API::Node source, API::Node sink) {
|
||||
source.getAValueReachableFromSource() = sink.asSink()
|
||||
or
|
||||
// Handle the case of an upstream class being the base class of an exposed own class
|
||||
//
|
||||
// class Foo extends external.BaseClass {}
|
||||
//
|
||||
// Here we want to ensure that `Instance(Foo)` is seen as subtype of `Instance(external.BaseClass)`.
|
||||
//
|
||||
// Although we have a dedicated sink node for `Instance(Foo)` we don't have dedicate source node for `Instance(external.BaseClass)`.
|
||||
//
|
||||
// However, there is always an `Instance` edge from the base class expression (`external.BaseClass`)
|
||||
// to the receiver node in subclass constructor (the implicit constructor of `Foo`), which always exists.
|
||||
// So we use the constructor receiver as the representative for `Instance(external.BaseClass)`.
|
||||
// (This will get simplified when migrating to Ruby-style API graphs, as both sides will have explicit API nodes).
|
||||
exists(DataFlow::ClassNode cls |
|
||||
source.asSource() = cls.getConstructor().getReceiver() and
|
||||
sink = API::Internal::getClassInstance(cls)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
## 0.8.13
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The `@precision` of the `js/unsafe-external-link` has been reduced to `low` to reflect the fact that modern browsers do not expose the opening window for such links. This mitigates the potential security risk of having a link with `target="_blank"`.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The call graph has been improved, leading to more alerts for data flow based queries.
|
||||
|
||||
## 0.8.12
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.11
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.10
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.9
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -9,6 +9,14 @@ of the origin page using <code>window.opener</code> unless link type <code>noope
|
||||
or <code>noreferrer</code> is specified. This is a potential security risk.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that only older browsers, where <code>target="_blank"</code> does not imply <code>rel="noopener"</code>,
|
||||
are affected by this vulnerability. Modern browsers implicitly add <code>rel="noopener"</code> to
|
||||
<code>target="_blank"</code> links.
|
||||
Refer to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#browser_compatibility">browser compatibility section
|
||||
on the anchor element</a> for details on which browsers implicitly add <code>rel="noopener"</code> to <code>target="_blank"</code> links.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* security
|
||||
* external/cwe/cwe-200
|
||||
* external/cwe/cwe-1022
|
||||
* @precision very-high
|
||||
* @precision low
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
@@ -12,7 +12,7 @@ import javascript
|
||||
|
||||
from Directive d
|
||||
where
|
||||
not d instanceof KnownDirective and
|
||||
not d instanceof Directive::KnownDirective and
|
||||
// ignore ":" pseudo-directive sometimes seen in dual-use shell/node.js scripts
|
||||
not d.getExpr().getStringValue() = ":" and
|
||||
// but exclude attribute top-levels: `<a href="javascript:'some-attribute-string'">`
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
* lines-of-code
|
||||
* debug
|
||||
* @id js/summary/lines-of-user-code
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The call graph has been improved, leading to more alerts for data flow based queries.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* `API::Node#getInstance()` now includes instances of subclasses, include transitive subclasses.
|
||||
The same changes applies to uses of the `Instance` token in data extensions.
|
||||
3
javascript/ql/src/change-notes/released/0.8.10.md
Normal file
3
javascript/ql/src/change-notes/released/0.8.10.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.10
|
||||
|
||||
No user-facing changes.
|
||||
3
javascript/ql/src/change-notes/released/0.8.11.md
Normal file
3
javascript/ql/src/change-notes/released/0.8.11.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.11
|
||||
|
||||
No user-facing changes.
|
||||
3
javascript/ql/src/change-notes/released/0.8.12.md
Normal file
3
javascript/ql/src/change-notes/released/0.8.12.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.12
|
||||
|
||||
No user-facing changes.
|
||||
9
javascript/ql/src/change-notes/released/0.8.13.md
Normal file
9
javascript/ql/src/change-notes/released/0.8.13.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 0.8.13
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The `@precision` of the `js/unsafe-external-link` has been reduced to `low` to reflect the fact that modern browsers do not expose the opening window for such links. This mitigates the potential security risk of having a link with `target="_blank"`.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The call graph has been improved, leading to more alerts for data flow based queries.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.8.9
|
||||
lastReleaseVersion: 0.8.13
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-queries
|
||||
version: 0.8.10-dev
|
||||
version: 0.8.14-dev
|
||||
groups:
|
||||
- javascript
|
||||
- queries
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from KnownDirective d
|
||||
from Directive::KnownDirective d
|
||||
select d, d.getDirectiveText()
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
typeModel
|
||||
| (aliases).Alias1 | aliases | Member[Alias1] |
|
||||
| (aliases).Alias1 | aliases | Member[Alias2] |
|
||||
| (aliases).Alias1 | aliases | Member[Alias3].Member[x] |
|
||||
| (aliases).Alias1 | aliases | Member[Alias4].Member[x].Member[x] |
|
||||
| (aliases).Alias1 | aliases | Member[AliasedClass] |
|
||||
| (aliases).Alias1.prototype | (aliases).Alias1 | Instance |
|
||||
| (aliases).Alias1.prototype.foo | (aliases).Alias1.prototype | Member[foo] |
|
||||
| (long-access-path).a.shortcut.d | long-access-path | Member[a].Member[b].Member[c].Member[d] |
|
||||
| (long-access-path).a.shortcut.d | long-access-path | Member[a].Member[shortcut].Member[d] |
|
||||
| (long-access-path).a.shortcut.d.e | (long-access-path).a.shortcut.d | Member[e] |
|
||||
| (reexport).func | reexport | Member[func] |
|
||||
| (return-this).FluentInterface | return-this | Member[FluentInterface] |
|
||||
| (return-this).FluentInterface.prototype | (return-this).FluentInterface | Instance |
|
||||
| (return-this).FluentInterface.prototype | (return-this).FluentInterface.prototype.bar | ReturnValue |
|
||||
| (return-this).FluentInterface.prototype | (return-this).FluentInterface.prototype.baz | ReturnValue |
|
||||
| (return-this).FluentInterface.prototype.bar | (return-this).FluentInterface.prototype | Member[bar] |
|
||||
| (return-this).FluentInterface.prototype.baz | (return-this).FluentInterface.prototype | Member[baz] |
|
||||
| (return-this).FluentInterface.prototype.foo | (return-this).FluentInterface.prototype | Member[foo] |
|
||||
| (return-this).FluentInterface.prototype.notFluent | (return-this).FluentInterface.prototype | Member[notFluent] |
|
||||
| (return-this).FluentInterface.prototype.notFluent2 | (return-this).FluentInterface.prototype | Member[notFluent2] |
|
||||
| (root-function).PublicClass | root-function | Member[PublicClass] |
|
||||
| (root-function).PublicClass.prototype | (root-function).PublicClass | Instance |
|
||||
| (root-function).PublicClass.prototype | root-function | ReturnValue |
|
||||
| (root-function).PublicClass.prototype.method | (root-function).PublicClass.prototype | Member[method] |
|
||||
| (semi-internal-class).PublicClass | semi-internal-class | Member[PublicClass] |
|
||||
| (semi-internal-class).PublicClass.prototype | (semi-internal-class).PublicClass | Instance |
|
||||
| (semi-internal-class).PublicClass.prototype | (semi-internal-class).SemiInternalClass.prototype.method | ReturnValue |
|
||||
| (semi-internal-class).PublicClass.prototype | (semi-internal-class).getAnonymous~expr2 | ReturnValue |
|
||||
| (semi-internal-class).PublicClass.prototype.publicMethod | (semi-internal-class).PublicClass.prototype | Member[publicMethod] |
|
||||
| (semi-internal-class).SemiInternalClass.prototype | (semi-internal-class).get | ReturnValue |
|
||||
| (semi-internal-class).SemiInternalClass.prototype.method | (semi-internal-class).SemiInternalClass.prototype | Member[method] |
|
||||
| (semi-internal-class).get | semi-internal-class | Member[get] |
|
||||
| (semi-internal-class).getAnonymous | semi-internal-class | Member[getAnonymous] |
|
||||
| (semi-internal-class).getAnonymous~expr1 | (semi-internal-class).getAnonymous | ReturnValue |
|
||||
| (semi-internal-class).getAnonymous~expr2 | (semi-internal-class).getAnonymous~expr1 | Member[method] |
|
||||
| (subclass).A | subclass | Member[A] |
|
||||
| (subclass).A.prototype | (subclass).A | Instance |
|
||||
| (subclass).A.prototype | (subclass).B.prototype | |
|
||||
| (subclass).A.prototype | (subclass).ExposedMidSubClass.prototype~expr1 | |
|
||||
| (subclass).A.prototype.a | (subclass).A.prototype | Member[a] |
|
||||
| (subclass).B | subclass | Member[B] |
|
||||
| (subclass).B.prototype | (subclass).B | Instance |
|
||||
| (subclass).B.prototype | (subclass).C.prototype | |
|
||||
| (subclass).B.prototype.b | (subclass).B.prototype | Member[b] |
|
||||
| (subclass).C | subclass | Member[C] |
|
||||
| (subclass).C.prototype | (subclass).C | Instance |
|
||||
| (subclass).C.prototype.c | (subclass).C.prototype | Member[c] |
|
||||
| (subclass).D | subclass | Member[D] |
|
||||
| (subclass).D.prototype | (subclass).D | Instance |
|
||||
| (subclass).D.prototype.d | (subclass).D.prototype | Member[d] |
|
||||
| (subclass).ExposedMidSubClass | subclass | Member[ExposedMidSubClass] |
|
||||
| (subclass).ExposedMidSubClass.prototype | (subclass).ExposedMidSubClass | Instance |
|
||||
| (subclass).ExposedMidSubClass.prototype.m | (subclass).ExposedMidSubClass.prototype | Member[m] |
|
||||
| (subclass).ExposedMidSubClass.prototype~expr1 | (subclass).ExposedMidSubClass.prototype | |
|
||||
| upstream-lib | (reexport).func | ReturnValue |
|
||||
| upstream-lib | reexport | Member[lib] |
|
||||
| upstream-lib.Type | (subclass).D.prototype | |
|
||||
| upstream-lib.XYZ | reexport | Member[x].Member[y].Member[z] |
|
||||
| upstream-lib.XYZ | reexport | Member[xy].Member[z] |
|
||||
summaryModel
|
||||
| (aliases).Alias1.prototype | | | Member[foo].ReturnValue | type |
|
||||
| (return-this).FluentInterface.prototype | | | Member[bar].ReturnValue | type |
|
||||
| (return-this).FluentInterface.prototype | | | Member[baz].ReturnValue | type |
|
||||
| (return-this).FluentInterface.prototype | | | Member[foo].ReturnValue | type |
|
||||
@@ -0,0 +1,7 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: typeModel
|
||||
data:
|
||||
- ["upstream-lib.XYZ", "upstream-lib", "Member[x].Member[y].Member[z]"]
|
||||
- ["upstream-lib.Type", "upstream-lib", "Member[Type].Instance"]
|
||||
@@ -0,0 +1,19 @@
|
||||
private import javascript
|
||||
private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
|
||||
private import semmle.javascript.frameworks.data.internal.ApiGraphModels as Shared
|
||||
|
||||
module ModelExportConfig implements ModelExportSig {
|
||||
predicate shouldContain(API::Node node) {
|
||||
node.getAValueReachingSink() instanceof DataFlow::FunctionNode
|
||||
}
|
||||
|
||||
predicate mustBeNamed(API::Node node) { shouldContain(node) }
|
||||
|
||||
predicate shouldContainType(string type) { Shared::isRelevantType(type) }
|
||||
}
|
||||
|
||||
module Exported = ModelExport<ModelExportConfig>;
|
||||
|
||||
query predicate typeModel = Exported::typeModel/3;
|
||||
|
||||
query predicate summaryModel = Exported::summaryModel/5;
|
||||
@@ -0,0 +1,9 @@
|
||||
export class AliasedClass {
|
||||
foo() { return this; }
|
||||
}
|
||||
|
||||
export const Alias1 = AliasedClass;
|
||||
export const Alias2 = AliasedClass;
|
||||
|
||||
export const Alias3 = { x: AliasedClass };
|
||||
export const Alias4 = { x: Alias3 };
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "aliases",
|
||||
"main": "aliases.js"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export const a = {
|
||||
b: {
|
||||
c: {
|
||||
d: {
|
||||
e: function() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
a.shortcut = a.b.c;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "long-access-path",
|
||||
"main": "long-access-path.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "reexport",
|
||||
"main": "reexport.js"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import * as lib from "upstream-lib";
|
||||
|
||||
export { lib };
|
||||
|
||||
export const x = lib.x;
|
||||
export const xy = lib.x.y;
|
||||
|
||||
export function func() {
|
||||
return lib;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "return-this",
|
||||
"main": "return-this.js"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export class FluentInterface {
|
||||
foo() {
|
||||
return this;
|
||||
}
|
||||
bar() {
|
||||
return this.foo();
|
||||
}
|
||||
baz() {
|
||||
return this.foo().bar().bar().foo();
|
||||
}
|
||||
notFluent() {
|
||||
this.foo();
|
||||
}
|
||||
notFluent2() {
|
||||
return this.notFluent2();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "root-function",
|
||||
"main": "root-function.js"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class C {
|
||||
method() {}
|
||||
}
|
||||
|
||||
module.exports = function() {
|
||||
return new C();
|
||||
}
|
||||
|
||||
module.exports.PublicClass = C;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "semi-internal-class",
|
||||
"main": "semi-internal-class.js"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export class PublicClass {
|
||||
publicMethod() {}
|
||||
}
|
||||
|
||||
class SemiInternalClass {
|
||||
method() {
|
||||
return new PublicClass();
|
||||
}
|
||||
}
|
||||
|
||||
export function get() {
|
||||
return new SemiInternalClass();
|
||||
}
|
||||
|
||||
export function getAnonymous() {
|
||||
return new (class {
|
||||
method() {
|
||||
return new PublicClass();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "subclass",
|
||||
"main": "subclass.js"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export class A {
|
||||
a() {}
|
||||
}
|
||||
|
||||
export class B extends A {
|
||||
b() {}
|
||||
}
|
||||
|
||||
export class C extends B {
|
||||
c() {}
|
||||
}
|
||||
|
||||
import * as upstream from "upstream-lib";
|
||||
|
||||
export class D extends upstream.Type {
|
||||
d() {}
|
||||
}
|
||||
|
||||
// Test case where subclass chain goes through an internal class
|
||||
class InternalMidClass extends A {}
|
||||
|
||||
export class ExposedMidSubClass extends InternalMidClass {
|
||||
m() {}
|
||||
}
|
||||
@@ -74,6 +74,10 @@ taintFlow
|
||||
| test.js:249:28:249:35 | source() | test.js:249:28:249:35 | source() |
|
||||
| test.js:252:15:252:22 | source() | test.js:252:15:252:22 | source() |
|
||||
| test.js:254:32:254:39 | source() | test.js:254:32:254:39 | source() |
|
||||
| test.js:262:10:262:31 | this.ba ... ource() | test.js:262:10:262:31 | this.ba ... ource() |
|
||||
| test.js:265:6:265:39 | new MyS ... ource() | test.js:265:6:265:39 | new MyS ... ource() |
|
||||
| test.js:269:10:269:31 | this.ba ... ource() | test.js:269:10:269:31 | this.ba ... ource() |
|
||||
| test.js:272:6:272:40 | new MyS ... ource() | test.js:272:6:272:40 | new MyS ... ource() |
|
||||
isSink
|
||||
| test.js:54:18:54:25 | source() | test-sink |
|
||||
| test.js:55:22:55:29 | source() | test-sink |
|
||||
|
||||
@@ -256,3 +256,17 @@ function fuzzy() {
|
||||
fuzzyCall(source()); // OK - does not come from 'testlib'
|
||||
require('blah').fuzzyCall(source()); // OK - does not come from 'testlib'
|
||||
}
|
||||
|
||||
class MySubclass extends testlib.BaseClass {
|
||||
foo() {
|
||||
sink(this.baseclassSource()); // NOT OK
|
||||
}
|
||||
}
|
||||
sink(new MySubclass().baseclassSource()); // NOT OK
|
||||
|
||||
class MySubclass2 extends MySubclass {
|
||||
foo2() {
|
||||
sink(this.baseclassSource()); // NOT OK
|
||||
}
|
||||
}
|
||||
sink(new MySubclass2().baseclassSource()); // NOT OK
|
||||
|
||||
@@ -80,6 +80,7 @@ class Sources extends ModelInput::SourceModelCsv {
|
||||
"testlib;Member[ParamDecoratorSource].DecoratedParameter;test-source",
|
||||
"testlib;Member[MethodDecorator].DecoratedMember.Parameter[0];test-source",
|
||||
"testlib;Member[MethodDecoratorWithArgs].ReturnValue.DecoratedMember.Parameter[0];test-source",
|
||||
"testlib;Member[BaseClass].Instance.Member[baseclassSource].ReturnValue;test-source",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
| UnknownDirective.js:12:5:12:17 | "use struct;" | Unknown directive: 'use struct;'. |
|
||||
| UnknownDirective.js:13:5:13:17 | "Use Strict"; | Unknown directive: 'Use Strict'. |
|
||||
| UnknownDirective.js:14:5:14:14 | "use bar"; | Unknown directive: 'use bar'. |
|
||||
| UnknownDirective.js:38:5:38:17 | "[0, 0, 0];"; | Unknown directive: '[0, 0, 0];'. |
|
||||
| UnknownDirective.js:39:5:39:65 | "[0, 0, ... , 0];"; | Unknown directive: '[0, 0, 0, 0, 0, 0, 0 ... (truncated)'. |
|
||||
| UnknownDirective.js:45:5:45:15 | ":nomunge"; | Unknown directive: ':nomunge'. |
|
||||
| UnknownDirective.js:46:5:46:30 | "foo(), ... munge"; | Unknown directive: 'foo(), bar, baz:nomu ... (truncated)'. |
|
||||
| UnknownDirective.js:40:5:40:17 | "[0, 0, 0];"; | Unknown directive: '[0, 0, 0];'. |
|
||||
| UnknownDirective.js:41:5:41:65 | "[0, 0, ... , 0];"; | Unknown directive: '[0, 0, 0, 0, 0, 0, 0 ... (truncated)'. |
|
||||
| UnknownDirective.js:47:5:47:15 | ":nomunge"; | Unknown directive: ':nomunge'. |
|
||||
| UnknownDirective.js:48:5:48:30 | "foo(), ... munge"; | Unknown directive: 'foo(), bar, baz:nomu ... (truncated)'. |
|
||||
|
||||
@@ -32,6 +32,8 @@ function good() {
|
||||
"ngNoInject"; // OK
|
||||
"deps foo"; // OK
|
||||
"deps bar"; // OK
|
||||
"use server"; // OK
|
||||
"use client"; // OK
|
||||
}
|
||||
|
||||
function data() {
|
||||
@@ -46,6 +48,6 @@ function yui() {
|
||||
"foo(), bar, baz:nomunge"; // NOT OK
|
||||
}
|
||||
|
||||
function babel_typeof(obj) {
|
||||
function babel_typeof(obj) {
|
||||
"@babel/helpers - typeof"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user