diff --git a/javascript/ql/lib/semmle/javascript/AST.qll b/javascript/ql/lib/semmle/javascript/AST.qll index 90b8494d166..bcde7bbaf4a 100644 --- a/javascript/ql/lib/semmle/javascript/AST.qll +++ b/javascript/ql/lib/semmle/javascript/AST.qll @@ -5,6 +5,8 @@ import javascript private import internal.StmtContainers private import semmle.javascript.internal.CachedStages +private import semmle.javascript.internal.TypeResolution +private import semmle.javascript.internal.BindingInfo /** * A program element corresponding to JavaScript code, such as an expression @@ -472,5 +474,22 @@ module AST { /** Gets the data flow node associated with this program element. */ DataFlow::ValueNode flow() { result = DataFlow::valueNode(this) } + + /** + * Gets information about the results of name-resolution for this expression. + * + * This can be used to map an expression to the class it refers to, or + * associate it with a named value coming from an dependency. + */ + ExprNameBindingNode getNameBinding() { result = this } + + /** + * Gets information about the type of this expression. + * + * This can be used to map an expression to the classes it may be an instance of + * (according to the type system), or to associate it with a named type coming + * from a dependency. + */ + TypeNameBindingNode getTypeBinding() { TypeResolution::valueHasType(this, result) } } } diff --git a/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll b/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll index 318ad2f8873..d9944b311ea 100644 --- a/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll +++ b/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll @@ -6,11 +6,20 @@ import javascript private import internal.StmtContainers private import internal.NameResolution private import internal.UnderlyingTypes +private import internal.BindingInfo /** * A type annotation, either in the form of a TypeScript type or a JSDoc comment. */ class TypeAnnotation extends @type_annotation, NodeInStmtContainer { + /** + * Gets information about the results of name-resolution for this type. + * + * This can be used to map a type name to the class/interface it refers to, or + * associate it with a named type coming from an dependency. + */ + TypeNameBindingNode getTypeBinding() { result = this } + /** Holds if this is the `any` type. */ predicate isAny() { none() } diff --git a/javascript/ql/lib/semmle/javascript/internal/BindingInfo.qll b/javascript/ql/lib/semmle/javascript/internal/BindingInfo.qll new file mode 100644 index 00000000000..e5d3c3e4819 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/internal/BindingInfo.qll @@ -0,0 +1,159 @@ +/** + * Provides a limited public interface to name/type resolution information. + */ + +private import javascript +private import semmle.javascript.internal.NameResolution +private import semmle.javascript.internal.TypeResolution +private import semmle.javascript.internal.UnderlyingTypes + +/** + * Interface for accessing name-resolution info about type names. + */ +class TypeNameBindingNode extends NameResolution::Node { + /** + * Holds if type refers to, or is an alias for, the given type name relative to the global scope. + * + * For example: + * ```ts + * var x: Document; // hasQualifiedName("Document") + * var x: Electron; // hasQualifiedName("Electron") + * var x: Electron.BrowserWindow; // hasQualifiedName("Electron.BrowserWindow") + * ``` + */ + predicate hasQualifiedName(string qualifiedName) { + NameResolution::nodeRefersToModule(this, "global", qualifiedName) + } + + /** + * Holds if this refers a value exported by the given module, with the given + * qualified name. If the `qualifiedName` is empty, this refers to the module itself. + * + * For example, the type annotations below have the following name bindings: + * ```ts + * import { Request } from "express"; + * + * var x: Request; // hasUnderlyingType("express", "Request") + * var x: Request | null; // no result (see hasUnderlyingType) + * var x: Request & { prop: string }; // no result (see hasUnderlyingType) + * + * interface CustomSubtype extends Request {} + * + * var x: CustomSubtype; // no result (see hasUnderlyingType) + * + * var x: typeof import("express"); // hasUnderlyingType("express", "") + * ``` + */ + predicate hasQualifiedName(string moduleName, string qualifiedName) { + NameResolution::nodeRefersToModule(this, moduleName, qualifiedName) + } + + /** + * Holds if this type refers to the given type exported from the given module, after + * unfolding unions and intersections, and following subtype relations. + * + * For example: + * ```ts + * import { Request } from "express"; + * + * var x: Request; // hasUnderlyingType("express", "Request") + * var x: Request | null; // hasUnderlyingType("express", "Request") + * var x: Request & { prop: string }; // hasUnderlyingType("express", "Request") + * + * interface CustomSubtype extends Request {} + * + * var x: CustomSubtype; // hasUnderlyingType("express", "Request") + * ``` + */ + predicate hasUnderlyingType(string moduleName, string qualifiedName) { + UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, qualifiedName) + } + + /** + * Holds if this type refers to the given type from the global scope, after + * unfolding unions and intersections, and following subtype relations. + * + * For example: + * ```ts + * var x: Document; // hasUnderlyingType("Document") + * var x: Document | null; // hasUnderlyingType("Document") + * var x: Document & { prop: string }; // hasUnderlyingType("Document") + * + * interface CustomSubtype extends Document {} + * + * var x: CustomSubtype; // hasUnderlyingType("Document") + * ``` + */ + predicate hasUnderlyingType(string qualifiedName) { + UnderlyingTypes::nodeHasUnderlyingType(this, qualifiedName) + } + + /** + * Gets the declaration of the type being referenced by this name. + * + * For example: + * ```ts + * class Foo {} + * + * type T = Foo; + * var x: T; // getTypeDefinition() maps T to the class Foo above + * ``` + * + * Note that this has no result for function-style classes referenced from + * a JSDoc comment. + */ + TypeDefinition getTypeDefinition() { TypeResolution::trackType(result) = this } + + /** + * Gets a class that this type refers to, after unfolding unions and intersections (but not subtyping). + * + * For example, the type of `x` maps to the class `C` in each example below: + * ```ts + * class C {} + * + * var x: C; + * var x: C | null; + * var x: C & { prop: string }; + * ``` + */ + DataFlow::ClassNode getAnUnderlyingClass() { + UnderlyingTypes::nodeHasUnderlyingClassType(this, result) + } +} + +/** + * Interface for accessing name-resolution info about expressions. + */ +class ExprNameBindingNode extends NameResolution::Node { + /** + * Holds if this refers a value exported by the given module, with the given + * qualified name. If the `qualifiedName` is empty, this refers to the module itself. + * + * For example, the type annotations below have the following name bindings: + * ```ts + * import * as f from "foo"; + * + * var x = f; // hasQualifiedName(f, "") + * var x = f.x.y; // hasQualifiedName(f, "x.y") + * ``` + */ + predicate hasQualifiedName(string moduleName, string qualifiedName) { + NameResolution::nodeRefersToModule(this, moduleName, qualifiedName) + } + + /** + * Gets the class, or function acting as a class, referenced by this name. + * + * ```ts + * class Foo {} + * const T = Foo; + * var x = T; // getClassNode() maps T to the class Foo above + * + * function Bar() {} + * Bar.prototype.blah = function() {}; + * const S = Bar; + * var x = S; // getClassNode() maps S to the function Bar above + * ``` + */ + DataFlow::ClassNode getClassNode() { NameResolution::nodeRefersToClass(this, result) } +} diff --git a/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll b/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll index 5725181baa0..7496b6c0482 100644 --- a/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll +++ b/javascript/ql/lib/semmle/javascript/internal/NameResolution.qll @@ -519,4 +519,19 @@ module NameResolution { cls.flowsTo(AccessPath::getAnAssignmentTo(name)) and not cls.getTopLevel().isExterns() // don't propagate externs classes } + + /** + * Holds if `node` refers to the given class. + */ + pragma[nomagic] + predicate nodeRefersToClass(Node node, DataFlow::ClassNode cls) { + exists(string name | + classHasGlobalName(cls, name) and + nodeRefersToModule(node, "global", name) + ) + or + trackClassValue(cls.getAstNode()) = node + or + trackFunctionValue(cls.getAstNode()) = node + } }