Merge branch 'js/graph-export' into js/vea-hacking-models

This commit is contained in:
Asger F
2024-04-09 14:41:25 +02:00
1257 changed files with 84093 additions and 32020 deletions

View File

@@ -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

View File

@@ -1,4 +0,0 @@
---
category: majorAnalysis
---
* Added support for TypeScript 5.4.

View File

@@ -0,0 +1,3 @@
## 0.8.10
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.8.11
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.8.12
No user-facing changes.

View File

@@ -0,0 +1,5 @@
## 0.8.13
### Major Analysis Improvements
* Added support for TypeScript 5.4.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.8.9
lastReleaseVersion: 0.8.13

View File

@@ -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

View File

@@ -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() }

View File

@@ -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 |

View File

@@ -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

View File

@@ -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.

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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.

View File

@@ -5,7 +5,7 @@
*/
import javascript
import codeql.serverless.ServerLess
private import codeql.serverless.ServerLess
private module YamlImpl implements Input {
import semmle.javascript.Files

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
)
)
}
}

View File

@@ -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)
)
}

View File

@@ -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

View File

@@ -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>

View File

@@ -10,7 +10,7 @@
* security
* external/cwe/cwe-200
* external/cwe/cwe-1022
* @precision very-high
* @precision low
*/
import javascript

View File

@@ -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'">`

View File

@@ -6,6 +6,7 @@
* @kind metric
* @tags summary
* lines-of-code
* debug
* @id js/summary/lines-of-user-code
*/

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* The call graph has been improved, leading to more alerts for data flow based queries.

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.8.10
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.8.11
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.8.12
No user-facing changes.

View 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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.8.9
lastReleaseVersion: 0.8.13

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-queries
version: 0.8.10-dev
version: 0.8.14-dev
groups:
- javascript
- queries

View File

@@ -1,4 +1,4 @@
import javascript
from KnownDirective d
from Directive::KnownDirective d
select d, d.getDirectiveText()

View File

@@ -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 |

View File

@@ -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"]

View File

@@ -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;

View File

@@ -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 };

View File

@@ -0,0 +1,4 @@
{
"name": "aliases",
"main": "aliases.js"
}

View File

@@ -0,0 +1,11 @@
export const a = {
b: {
c: {
d: {
e: function() {}
}
}
}
};
a.shortcut = a.b.c;

View File

@@ -0,0 +1,4 @@
{
"name": "long-access-path",
"main": "long-access-path.js"
}

View File

@@ -0,0 +1,4 @@
{
"name": "reexport",
"main": "reexport.js"
}

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
{
"name": "return-this",
"main": "return-this.js"
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "root-function",
"main": "root-function.js"
}

View File

@@ -0,0 +1,9 @@
class C {
method() {}
}
module.exports = function() {
return new C();
}
module.exports.PublicClass = C;

View File

@@ -0,0 +1,4 @@
{
"name": "semi-internal-class",
"main": "semi-internal-class.js"
}

View File

@@ -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();
}
});
}

View File

@@ -0,0 +1,4 @@
{
"name": "subclass",
"main": "subclass.js"
}

View File

@@ -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() {}
}

View File

@@ -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 |

View File

@@ -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

View File

@@ -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",
]
}
}

View File

@@ -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)'. |

View File

@@ -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"
}
}