From d3e3c11fa6ed122f459cd2eab8d7f8b313ea3253 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 8 Oct 2020 21:16:40 +0200 Subject: [PATCH 01/36] add printAst query for printing JS/TS/JSON/YAML/HTML --- javascript/ql/src/printAst.ql | 28 + .../ql/src/semmle/javascript/Classes.qll | 56 +- .../src/semmle/javascript/ES2015Modules.qll | 8 + javascript/ql/src/semmle/javascript/Expr.qll | 74 +- javascript/ql/src/semmle/javascript/HTML.qll | 32 + javascript/ql/src/semmle/javascript/JSON.qll | 22 +- javascript/ql/src/semmle/javascript/JSX.qll | 14 +- .../ql/src/semmle/javascript/Locations.qll | 5 + .../ql/src/semmle/javascript/PrintAst.qll | 714 +++++ javascript/ql/src/semmle/javascript/Stmt.qll | 56 +- .../ql/src/semmle/javascript/Templates.qll | 6 + .../ql/src/semmle/javascript/TypeScript.qll | 104 +- .../ql/src/semmle/javascript/Variables.qll | 20 +- javascript/ql/src/semmle/javascript/YAML.qll | 14 +- .../library-tests/Arrays/printAst.expected | 954 ++++++ .../ql/test/library-tests/Arrays/printAst.ql | 2 + .../printAst.expected | 84 + .../HTMLElementAndHTMLAttribute/printAst.ql | 2 + .../test/library-tests/JSON/printAst.expected | 44 + .../ql/test/library-tests/JSON/printAst.ql | 2 + .../test/library-tests/JSX/printAst.expected | 308 ++ .../ql/test/library-tests/JSX/printAst.ql | 2 + .../TypeAnnotations/printAst.expected | 2608 +++++++++++++++++ .../TypeScript/TypeAnnotations/printAst.ql | 2 + .../TypeScript/Types/printAst.expected | 1305 +++++++++ .../TypeScript/Types/printAst.ql | 2 + .../test/library-tests/YAML/printAst.expected | 151 + .../ql/test/library-tests/YAML/printAst.ql | 2 + 28 files changed, 6592 insertions(+), 29 deletions(-) create mode 100644 javascript/ql/src/printAst.ql create mode 100644 javascript/ql/src/semmle/javascript/PrintAst.qll create mode 100644 javascript/ql/test/library-tests/Arrays/printAst.expected create mode 100644 javascript/ql/test/library-tests/Arrays/printAst.ql create mode 100644 javascript/ql/test/library-tests/HTML/HTMLElementAndHTMLAttribute/printAst.expected create mode 100644 javascript/ql/test/library-tests/HTML/HTMLElementAndHTMLAttribute/printAst.ql create mode 100644 javascript/ql/test/library-tests/JSON/printAst.expected create mode 100644 javascript/ql/test/library-tests/JSON/printAst.ql create mode 100644 javascript/ql/test/library-tests/JSX/printAst.expected create mode 100644 javascript/ql/test/library-tests/JSX/printAst.ql create mode 100644 javascript/ql/test/library-tests/TypeScript/TypeAnnotations/printAst.expected create mode 100644 javascript/ql/test/library-tests/TypeScript/TypeAnnotations/printAst.ql create mode 100644 javascript/ql/test/library-tests/TypeScript/Types/printAst.expected create mode 100644 javascript/ql/test/library-tests/TypeScript/Types/printAst.ql create mode 100644 javascript/ql/test/library-tests/YAML/printAst.expected create mode 100644 javascript/ql/test/library-tests/YAML/printAst.ql diff --git a/javascript/ql/src/printAst.ql b/javascript/ql/src/printAst.ql new file mode 100644 index 00000000000..564a5f15a53 --- /dev/null +++ b/javascript/ql/src/printAst.ql @@ -0,0 +1,28 @@ +/** + * @name Print AST + * @description Outputs a representation of a file's Abstract Syntax Tree. This + * query is used by the VS Code extension. + * @id js/print-ast + * @kind graph + * @tags ide-contextual-queries/print-ast + */ + +import javascript +import semmle.javascript.PrintAst +import definitions + +/** + * The source file to generate an AST from. + */ +external string selectedSourceFile(); + +class PrintAstConfigurationOverride extends PrintAstConfiguration { + /** + * Holds if the location matches the selected file in the VS Code extension and + * the element is not a synthetic constructor. + */ + override predicate shouldPrint(Locatable e, Location l) { + super.shouldPrint(e, l) and + l.getFile() = getEncodedFile(selectedSourceFile()) + } +} diff --git a/javascript/ql/src/semmle/javascript/Classes.qll b/javascript/ql/src/semmle/javascript/Classes.qll index 93eead18658..82e782c4690 100644 --- a/javascript/ql/src/semmle/javascript/Classes.qll +++ b/javascript/ql/src/semmle/javascript/Classes.qll @@ -256,6 +256,8 @@ class ClassDefinition extends @class_definition, ClassOrInterface, AST::ValueNod ClassDefinition getSuperClassDefinition() { result = getSuperClass().analyze().getAValue().(AbstractClass).getClass() } + + override string getAPrimaryQlClass() { result = "ClassDefinition" } } /** @@ -342,6 +344,8 @@ private class ClassInitializedMember extends MemberDeclaration { ClassInitializedMember() { this instanceof MethodDefinition or this.isStatic() } int getIndex() { properties(this, _, result, _, _) } + + override string getAPrimaryQlClass() { result = "ClassInitializedMember" } } /** @@ -361,6 +365,8 @@ class SuperExpr extends @super_expr, Expr { * which is the nearest enclosing non-arrow function. */ Function getBinder() { result = getEnclosingFunction().getThisBinder() } + + override string getAPrimaryQlClass() { result = "SuperExpr" } } /** @@ -412,6 +418,8 @@ class SuperPropAccess extends PropAccess { */ class NewTargetExpr extends @newtarget_expr, Expr { override predicate isImpure() { none() } + + override string getAPrimaryQlClass() { result = "NewTargetExpr" } } /** @@ -574,6 +582,8 @@ class MemberDeclaration extends @property, Documentable { * True if this is abstract, ambient, or an overload signature. */ predicate isSignature() { not isConcrete() } + + override string getAPrimaryQlClass() { result = "MemberDeclaration" } } /** @@ -748,7 +758,9 @@ private predicate hasOverloadedConstructorCallSignature(ClassOrInterface type) { * } * ``` */ -class MethodDefinition extends MethodDeclaration, MemberDefinition { } +class MethodDefinition extends MethodDeclaration, MemberDefinition { + override string getAPrimaryQlClass() { result = "MethodDefinition" } +} /** * A method signature declared in a class or interface, that is, a method without a function body. @@ -763,7 +775,9 @@ class MethodDefinition extends MethodDeclaration, MemberDefinition { } * * Note that TypeScript call signatures are not considered method signatures. */ -class MethodSignature extends MethodDeclaration, MemberSignature { } +class MethodSignature extends MethodDeclaration, MemberSignature { + override string getAPrimaryQlClass() { result = "MethodSignature" } +} /** * A constructor declaration in a class, either a concrete definition or a signature without a body. @@ -792,6 +806,8 @@ class ConstructorDeclaration extends MethodDeclaration { /** Holds if this is a synthetic default constructor. */ predicate isSynthetic() { getLocation().isEmpty() } + + override string getAPrimaryQlClass() { result = "ConstructorDeclaration" } } /** @@ -813,7 +829,9 @@ class ConstructorDeclaration extends MethodDeclaration { * } * ``` */ -class ConstructorDefinition extends ConstructorDeclaration, MethodDefinition { } +class ConstructorDefinition extends ConstructorDeclaration, MethodDefinition { + override string getAPrimaryQlClass() { result = "ConstructorDefinition" } +} /** * A constructor signature declared in a class, that is, a constructor without a function body. @@ -824,7 +842,9 @@ class ConstructorDefinition extends ConstructorDeclaration, MethodDefinition { } * } * ``` */ -class ConstructorSignature extends ConstructorDeclaration, MethodSignature { } +class ConstructorSignature extends ConstructorDeclaration, MethodSignature { + override string getAPrimaryQlClass() { result = "ConstructorSignature" } +} /** * A function generated by the extractor to implement a synthetic default constructor. @@ -925,7 +945,9 @@ abstract class AccessorMethodSignature extends MethodSignature, AccessorMethodDe * } * ``` */ -class GetterMethodDeclaration extends AccessorMethodDeclaration, @property_getter { } +class GetterMethodDeclaration extends AccessorMethodDeclaration, @property_getter { + override string getAPrimaryQlClass() { result = "GetterMethodDeclaration" } +} /** * A concrete getter method definition in a class, that is, a getter method with a function body. @@ -945,7 +967,9 @@ class GetterMethodDeclaration extends AccessorMethodDeclaration, @property_gette * } * ``` */ -class GetterMethodDefinition extends GetterMethodDeclaration, AccessorMethodDefinition { } +class GetterMethodDefinition extends GetterMethodDeclaration, AccessorMethodDefinition { + override string getAPrimaryQlClass() { result = "GetterMethodDefinition" } +} /** * A getter method signature declared in a class or interface, that is, a getter method without a function body. @@ -958,7 +982,9 @@ class GetterMethodDefinition extends GetterMethodDeclaration, AccessorMethodDefi * } * ``` */ -class GetterMethodSignature extends GetterMethodDeclaration, AccessorMethodSignature { } +class GetterMethodSignature extends GetterMethodDeclaration, AccessorMethodSignature { + override string getAPrimaryQlClass() { result = "GetterMethodSignature" } +} /** * A setter method declaration in a class or interface, either a concrete definition or a signature without a body. @@ -981,7 +1007,9 @@ class GetterMethodSignature extends GetterMethodDeclaration, AccessorMethodSigna * } * ``` */ -class SetterMethodDeclaration extends AccessorMethodDeclaration, @property_setter { } +class SetterMethodDeclaration extends AccessorMethodDeclaration, @property_setter { + override string getAPrimaryQlClass() { result = "SetterMethodDeclaration" } +} /** * A concrete setter method definition in a class, that is, a setter method with a function body @@ -1000,7 +1028,9 @@ class SetterMethodDeclaration extends AccessorMethodDeclaration, @property_sette * } * ``` */ -class SetterMethodDefinition extends SetterMethodDeclaration, AccessorMethodDefinition { } +class SetterMethodDefinition extends SetterMethodDeclaration, AccessorMethodDefinition { + override string getAPrimaryQlClass() { result = "SetterMethodDefinition" } +} /** * A setter method signature declared in a class or interface, that is, a setter method without a function body. @@ -1013,7 +1043,9 @@ class SetterMethodDefinition extends SetterMethodDeclaration, AccessorMethodDefi * } * ``` */ -class SetterMethodSignature extends SetterMethodDeclaration, AccessorMethodSignature { } +class SetterMethodSignature extends SetterMethodDeclaration, AccessorMethodSignature { + override string getAPrimaryQlClass() { result = "SetterMethodSignature" } +} /** * A field declaration in a class or interface, either a concrete definition or an abstract or ambient field signature. @@ -1047,6 +1079,8 @@ class FieldDeclaration extends MemberDeclaration, @field { /** Holds if this is a TypeScript field marked as definitely assigned with the `!` operator. */ predicate hasDefiniteAssignmentAssertion() { has_definite_assignment_assertion(this) } + + override string getAPrimaryQlClass() { result = "FieldDeclaration" } } /** @@ -1203,4 +1237,6 @@ class IndexSignature extends @index_signature, MemberSignature { override InterfaceDefinition getDeclaringType() { result = MemberSignature.super.getDeclaringType() } + + override string getAPrimaryQlClass() { result = "IndexSignature" } } diff --git a/javascript/ql/src/semmle/javascript/ES2015Modules.qll b/javascript/ql/src/semmle/javascript/ES2015Modules.qll index 47a8ff426ae..a8b189a85fd 100644 --- a/javascript/ql/src/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/src/semmle/javascript/ES2015Modules.qll @@ -79,6 +79,8 @@ class ImportDeclaration extends Stmt, Import, @import_declaration { /** Holds if this is declared with the `type` keyword, so it only imports types. */ predicate isTypeOnly() { has_type_keyword(this) } + + override string getAPrimaryQlClass() { result = "ImportDeclaration" } } /** A literal path expression appearing in an `import` declaration. */ @@ -129,6 +131,8 @@ class ImportSpecifier extends Expr, @import_specifier { /** Gets the local variable into which this specifier imports. */ VarDecl getLocal() { result = getChildExpr(1) } + + override string getAPrimaryQlClass() { result = "ImportSpecifier" } } /** @@ -262,6 +266,8 @@ abstract class ExportDeclaration extends Stmt, @export_declaration { /** Holds if is declared with the `type` keyword, so only types are exported. */ predicate isTypeOnly() { has_type_keyword(this) } + + override string getAPrimaryQlClass() { result = "ExportDeclaration" } } /** @@ -511,6 +517,8 @@ class ExportSpecifier extends Expr, @exportspecifier { * an exported name since it does not export a unique symbol. */ string getExportedName() { result = getExported().getName() } + + override string getAPrimaryQlClass() { result = "ExportSpecifier" } } /** diff --git a/javascript/ql/src/semmle/javascript/Expr.qll b/javascript/ql/src/semmle/javascript/Expr.qll index 016a740c785..6126931bc5b 100644 --- a/javascript/ql/src/semmle/javascript/Expr.qll +++ b/javascript/ql/src/semmle/javascript/Expr.qll @@ -273,6 +273,8 @@ private DataFlow::Node getCatchParameterFromStmt(Stmt stmt) { class Identifier extends @identifier, ExprOrType { /** Gets the name of this identifier. */ string getName() { literals(result, _, this) } + + override string getAPrimaryQlClass() { result = "Identifier" } } /** @@ -290,6 +292,8 @@ class Identifier extends @identifier, ExprOrType { */ class Label extends @label, Identifier, Expr { override predicate isImpure() { none() } + + override string getAPrimaryQlClass() { result = "Label" } } /** @@ -317,6 +321,8 @@ class Literal extends @literal, Expr { string getRawValue() { literals(_, result, this) } override predicate isImpure() { none() } + + override string getAPrimaryQlClass() { result = "Literal" } } /** @@ -341,6 +347,8 @@ class ParExpr extends @par_expr, Expr { override Expr getUnderlyingValue() { result = getExpression().getUnderlyingValue() } override Expr getUnderlyingReference() { result = getExpression().getUnderlyingReference() } + + override string getAPrimaryQlClass() { result = "ParExpr" } } /** @@ -462,6 +470,8 @@ class RegExpLiteral extends @regexp_literal, Literal, RegExpParent { /** Holds if this regular expression has an `s` flag. */ predicate isDotAll() { RegExp::isDotAll(getFlags()) } + + override string getAPrimaryQlClass() { result = "RegExpLiteral" } } /** @@ -491,6 +501,8 @@ class ThisExpr extends @this_expr, Expr { or result = getContainer().(TopLevel) } + + override string getAPrimaryQlClass() { result = "ThisExpr" } } /** @@ -528,6 +540,8 @@ class ArrayExpr extends @array_expr, Expr { predicate hasOmittedElement() { elementIsOmitted(_) } override predicate isImpure() { getAnElement().isImpure() } + + override string getAPrimaryQlClass() { result = "ArrayExpr" } } /** @@ -568,6 +582,8 @@ class ObjectExpr extends @obj_expr, Expr { predicate hasTrailingComma() { this.getLastToken().getPreviousToken().getValue() = "," } override predicate isImpure() { getAProperty().isImpure() } + + override string getAPrimaryQlClass() { result = "ObjectExpr" } } /** @@ -668,6 +684,8 @@ class Property extends @property, Documentable { * decorators `@A` and `@B`. */ Decorator getADecorator() { result = getDecorator(_) } + + override string getAPrimaryQlClass() { result = "Property" } } /** @@ -791,6 +809,8 @@ class FunctionExpr extends @function_expr, Expr, Function { override StmtContainer getEnclosingContainer() { result = Expr.super.getContainer() } override predicate isImpure() { none() } + + override string getAPrimaryQlClass() { result = "FunctionExpr" } } /** @@ -811,6 +831,8 @@ class ArrowFunctionExpr extends @arrow_function_expr, Expr, Function { override predicate isImpure() { none() } override Function getThisBinder() { result = getEnclosingContainer().(Function).getThisBinder() } + + override string getAPrimaryQlClass() { result = "ArrowFunctionExpr" } } /** @@ -838,6 +860,8 @@ class SeqExpr extends @seq_expr, Expr { override predicate isImpure() { getAnOperand().isImpure() } override Expr getUnderlyingValue() { result = getLastOperand().getUnderlyingValue() } + + override string getAPrimaryQlClass() { result = "SeqExpr" } } /** @@ -866,6 +890,8 @@ class ConditionalExpr extends @conditional_expr, Expr { getCondition().isImpure() or getABranch().isImpure() } + + override string getAPrimaryQlClass() { result = "ConditionalExpr" } } /** @@ -986,7 +1012,9 @@ class InvokeExpr extends @invokeexpr, Expr { * new Array(16) * ``` */ -class NewExpr extends @new_expr, InvokeExpr { } +class NewExpr extends @new_expr, InvokeExpr { + override string getAPrimaryQlClass() { result = "NewExpr" } +} /** * A function call expression. @@ -1005,6 +1033,8 @@ class CallExpr extends @call_expr, InvokeExpr { * is invoked, if any. */ Expr getReceiver() { result = getCallee().(PropAccess).getBase() } + + override string getAPrimaryQlClass() { result = "CallExpr" } } /** @@ -1037,6 +1067,8 @@ class MethodCallExpr extends CallExpr { /** Holds if this invocation calls method `m` on expression `base`. */ predicate calls(Expr base, string m) { getMethodRef().accesses(base, m) } + + override string getAPrimaryQlClass() { result = "MethodCallExpr" } } /** @@ -1103,6 +1135,8 @@ class DotExpr extends @dot_expr, PropAccess { Identifier getProperty() { result = getChildExpr(1) } override predicate isImpure() { getBase().isImpure() } + + override string getAPrimaryQlClass() { result = "DotExpr" } } /** @@ -1124,6 +1158,8 @@ class IndexExpr extends @index_expr, PropAccess { getBase().isImpure() or getIndex().isImpure() } + + override string getAPrimaryQlClass() { result = "IndexExpr" } } /** @@ -1148,6 +1184,8 @@ class UnaryExpr extends @unaryexpr, Expr { override ControlFlowNode getFirstControlFlowNode() { result = getOperand().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "UnaryExpr" } } /** @@ -1258,6 +1296,8 @@ class DeleteExpr extends @delete_expr, UnaryExpr { */ class SpreadElement extends @spread_element, UnaryExpr { override string getOperator() { result = "..." } + + override string getAPrimaryQlClass() { result = "SpreadElement" } } /** @@ -1314,6 +1354,8 @@ class BinaryExpr extends @binaryexpr, Expr { result = c4 - c3 - 1 ) } + + override string getAPrimaryQlClass() { result = "BinaryExpr" } } /** @@ -1846,6 +1888,8 @@ class Assignment extends @assignment, Expr { */ class AssignExpr extends @assign_expr, Assignment { override Expr getUnderlyingValue() { result = getRhs().getUnderlyingValue() } + + override string getAPrimaryQlClass() { result = "AssignExpr" } } private class TCompoundAssignExpr = @@ -1864,7 +1908,9 @@ private class TCompoundAssignExpr = * x /= 2 * ``` */ -class CompoundAssignExpr extends TCompoundAssignExpr, Assignment { } +class CompoundAssignExpr extends TCompoundAssignExpr, Assignment { + override string getAPrimaryQlClass() { result = "CompoundAssignExpr" } +} /** * A compound add-assign expression. @@ -2056,6 +2102,8 @@ class UpdateExpr extends @updateexpr, Expr { override ControlFlowNode getFirstControlFlowNode() { result = getOperand().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "UpdateExpr" } } /** @@ -2137,6 +2185,8 @@ class YieldExpr extends @yield_expr, Expr { or not exists(getOperand()) and result = this } + + override string getAPrimaryQlClass() { result = "YieldExpr" } } /** @@ -2194,6 +2244,8 @@ class ComprehensionExpr extends @comprehension_expr, Expr { predicate isPostfix() { exists(Token tk | tk = getFirstToken().getNextToken() | not tk.getValue().regexpMatch("if|for")) } + + override string getAPrimaryQlClass() { result = "ComprehensionExpr" } } /** @@ -2246,6 +2298,8 @@ class ComprehensionBlock extends @comprehension_block, Expr { getIterator().isImpure() or getDomain().isImpure() } + + override string getAPrimaryQlClass() { result = "ComprehensionBlock" } } /** @@ -2468,6 +2522,8 @@ class LegacyLetExpr extends Expr, @legacy_letexpr { /** Gets the expression this `let` expression scopes over. */ Expr getBody() { result = getChildExpr(-1) } + + override string getAPrimaryQlClass() { result = "LegacyLetExpr" } } /** @@ -2569,6 +2625,8 @@ class AwaitExpr extends @await_expr, Expr { override ControlFlowNode getFirstControlFlowNode() { result = getOperand().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "AwaitExpr" } } /** @@ -2586,6 +2644,8 @@ class AwaitExpr extends @await_expr, Expr { */ class FunctionSentExpr extends @function_sent_expr, Expr { override predicate isImpure() { none() } + + override string getAPrimaryQlClass() { result = "FunctionSentExpr" } } /** @@ -2621,6 +2681,8 @@ class Decorator extends @decorator, Expr { override ControlFlowNode getFirstControlFlowNode() { result = getExpression().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "Decorator" } } /** @@ -2687,6 +2749,8 @@ class FunctionBindExpr extends @bind_expr, Expr { or not exists(getObject()) and result = getCallee().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "FunctionBindExpr" } } /** @@ -2711,6 +2775,8 @@ class DynamicImportExpr extends @dynamic_import, Expr, Import { override Module getEnclosingModule() { result = getTopLevel() } override DataFlow::Node getImportedModuleNode() { result = DataFlow::valueNode(this) } + + override string getAPrimaryQlClass() { result = "DynamicImportExpr" } } /** A literal path expression appearing in a dynamic import. */ @@ -2734,6 +2800,8 @@ private class LiteralDynamicImportPath extends PathExpr, ConstantString { */ class OptionalUse extends Expr, @optionalchainable { OptionalUse() { isOptionalChaining(this) } + + override string getAPrimaryQlClass() { result = "OptionalUse" } } private class ChainElem extends Expr, @optionalchainable { @@ -2775,4 +2843,6 @@ class OptionalChainRoot extends ChainElem { */ class ImportMetaExpr extends @import_meta_expr, Expr { override predicate isImpure() { none() } + + override string getAPrimaryQlClass() { result = "ImportMetaExpr" } } diff --git a/javascript/ql/src/semmle/javascript/HTML.qll b/javascript/ql/src/semmle/javascript/HTML.qll index c8418066b49..898c0d01697 100644 --- a/javascript/ql/src/semmle/javascript/HTML.qll +++ b/javascript/ql/src/semmle/javascript/HTML.qll @@ -80,6 +80,8 @@ module HTML { } override string toString() { result = "<" + getName() + ">..." } + + override string getAPrimaryQlClass() { result = "HTML::Element" } } /** @@ -124,6 +126,34 @@ module HTML { string getValue() { xmlAttrs(this, _, _, result, _, _) } override string toString() { result = getName() + "=" + getValue() } + + /** + * Gets the inline script of this attribute, if any. + */ + CodeInAttribute getCodeInAttribute() { + exists( + string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2, + int el2, int ec2 + | + l1 = getLocation() and + l2 = result.getLocation() and + l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and + l2.hasLocationInfo(f, sl2, sc2, el2, ec2) + | + ( + sl1 = sl2 and sc1 < sc2 + or + sl1 < sl2 + ) and + ( + el1 = el2 and ec1 > ec2 + or + el1 > el2 + ) + ) + } + + override string getAPrimaryQlClass() { result = "HTML::Attribute" } } /** @@ -227,6 +257,8 @@ module HTML { result = getInlineScript() or result = resolveSource() } + + override string getAPrimaryQlClass() { result = "HTML::ScriptElement" } } /** diff --git a/javascript/ql/src/semmle/javascript/JSON.qll b/javascript/ql/src/semmle/javascript/JSON.qll index c67f293c401..f0f4f59e98e 100644 --- a/javascript/ql/src/semmle/javascript/JSON.qll +++ b/javascript/ql/src/semmle/javascript/JSON.qll @@ -40,6 +40,8 @@ class JSONValue extends @json_value, Locatable { result = loc.getFile() ) } + + override string getAPrimaryQlClass() { result = "JSONValue" } } /** @@ -72,7 +74,9 @@ abstract class JSONPrimitiveValue extends JSONValue { * null * ``` */ -class JSONNull extends @json_null, JSONPrimitiveValue { } +class JSONNull extends @json_null, JSONPrimitiveValue { + override string getAPrimaryQlClass() { result = "JSONNull" } +} /** * A JSON-encoded Boolean value. @@ -84,7 +88,9 @@ class JSONNull extends @json_null, JSONPrimitiveValue { } * false * ``` */ -class JSONBoolean extends @json_boolean, JSONPrimitiveValue { } +class JSONBoolean extends @json_boolean, JSONPrimitiveValue { + override string getAPrimaryQlClass() { result = "JSONBoolean" } +} /** * A JSON-encoded number. @@ -96,7 +102,9 @@ class JSONBoolean extends @json_boolean, JSONPrimitiveValue { } * 1.0 * ``` */ -class JSONNumber extends @json_number, JSONPrimitiveValue { } +class JSONNumber extends @json_number, JSONPrimitiveValue { + override string getAPrimaryQlClass() { result = "JSONNumber" } +} /** * A JSON-encoded string value. @@ -107,7 +115,9 @@ class JSONNumber extends @json_number, JSONPrimitiveValue { } * "a string" * ``` */ -class JSONString extends @json_string, JSONPrimitiveValue { } +class JSONString extends @json_string, JSONPrimitiveValue { + override string getAPrimaryQlClass() { result = "JSONString" } +} /** * A JSON-encoded array. @@ -124,6 +134,8 @@ class JSONArray extends @json_array, JSONValue { /** Gets the string value of the `i`th element of this array. */ string getElementStringValue(int i) { result = getElementValue(i).(JSONString).getValue() } + + override string getAPrimaryQlClass() { result = "JSONArray" } } /** @@ -141,6 +153,8 @@ class JSONObject extends @json_object, JSONValue { /** Gets the string value of property `name` of this object. */ string getPropStringValue(string name) { result = getPropValue(name).(JSONString).getValue() } + + override string getAPrimaryQlClass() { result = "JSONObject" } } /** diff --git a/javascript/ql/src/semmle/javascript/JSX.qll b/javascript/ql/src/semmle/javascript/JSX.qll index b73b720bf6c..3b5c2910218 100644 --- a/javascript/ql/src/semmle/javascript/JSX.qll +++ b/javascript/ql/src/semmle/javascript/JSX.qll @@ -26,6 +26,8 @@ class JSXNode extends Expr, @jsx_element { * Gets the parent JSX element or fragment of this element. */ JSXNode getJsxParent() { this = result.getABodyElement() } + + override string getAPrimaryQlClass() { result = "JSXNode" } } /** @@ -61,6 +63,8 @@ class JSXElement extends JSXNode { override ControlFlowNode getFirstControlFlowNode() { result = getNameExpr().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "JSXElement" } } /** @@ -80,6 +84,8 @@ class JSXFragment extends JSXNode { or not exists(getABodyElement()) and result = this } + + override string getAPrimaryQlClass() { result = "JSXFragment" } } /** @@ -124,6 +130,8 @@ class JSXAttribute extends ASTNode, @jsx_attribute { } override string toString() { properties(this, _, _, _, result) } + + override string getAPrimaryQlClass() { result = "JSXAttribute" } } /** @@ -163,6 +171,8 @@ class JSXQualifiedName extends Expr, @jsx_qualified_name { override ControlFlowNode getFirstControlFlowNode() { result = getNamespace().getFirstControlFlowNode() } + + override string getAPrimaryQlClass() { result = "JSXQualifiedName" } } /** @@ -214,7 +224,9 @@ class JSXName extends Expr { * { /* TBD */ } * */ -class JSXEmptyExpr extends Expr, @jsx_empty_expr { } +class JSXEmptyExpr extends Expr, @jsx_empty_expr { + override string getAPrimaryQlClass() { result = "JSXEmptyExpr" } +} /** * A legacy `@jsx` pragma. diff --git a/javascript/ql/src/semmle/javascript/Locations.qll b/javascript/ql/src/semmle/javascript/Locations.qll index 5f6a0050359..a84bcd18308 100644 --- a/javascript/ql/src/semmle/javascript/Locations.qll +++ b/javascript/ql/src/semmle/javascript/Locations.qll @@ -131,6 +131,11 @@ class Locatable extends @locatable { // to be overridden by subclasses none() } + + /** + * Gets the primary QL class for the Locatable. + */ + string getAPrimaryQlClass() { result = "???" } } /** diff --git a/javascript/ql/src/semmle/javascript/PrintAst.qll b/javascript/ql/src/semmle/javascript/PrintAst.qll new file mode 100644 index 00000000000..26b9faa9d67 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/PrintAst.qll @@ -0,0 +1,714 @@ +/** + * Provides queries to pretty-print a JavaScript AST as a graph. + * + * By default, this will print the AST for all elements in the database. To change this behavior, + * extend `PrintAstConfiguration` and override `shouldPrint` to hold for only the elements + * you wish to view the AST for. + */ + +import javascript + +private newtype TPrintAstConfiguration = MkPrintAstConfiguration() + +/** + * The query can extend this class to control which elements are printed. + */ +class PrintAstConfiguration extends TPrintAstConfiguration { + /** + * Gets a textual representation of this `PrintAstConfiguration`. + */ + string toString() { result = "PrintAstConfiguration" } + + /** + * Controls whether the `Element` should be considered for AST printing. + * By default it checks whether the `Element` `e` belongs to `Location` `l`. + */ + predicate shouldPrint(Locatable e, Location l) { l = e.getLocation() } +} + +private predicate shouldPrint(Locatable e, Location l) { + exists(PrintAstConfiguration config | config.shouldPrint(e, l)) +} + +/** Holds if the given element does not need to be rendered in the AST, due to being compiler-generated or being a `TopLevel`. */ +private predicate isNotNeeded(Locatable el) { + exists(ClassDefinition c, ConstructorDeclaration constructor | + constructor = c.getConstructor() and + constructor.isSynthetic() and + el = constructor + ) + or + el instanceof TopLevel and + el.getLocation().getStartLine() = 0 and + el.getLocation().getStartColumn() = 0 + or + exists(ASTNode parent | isNotNeeded(parent) and not parent instanceof TopLevel | + el = parent.getAChild() + ) + or + // relaxing aggresive type inference. + none() +} + +/** + * Retrieves the canonical QL class(es) for entity `el` + */ +private string getQlClass(Locatable el) { + result = "[" + concat(el.getAPrimaryQlClass(), ",") + "] " + // Alternative implementation -- do not delete. It is useful for QL class discovery. + // not el.getAPrimaryQlClass() = "???" and result = "[" + concat(el.getAPrimaryQlClass(), ",") + "] " or el.getAPrimaryQlClass() = "???" and result = "??[" + concat(el.getAQlClass(), ",") + "] " +} + +/** + * Printed nodes for different file types. + */ +private newtype TPrintAstNode = + // JavaScript / TypeScript + TElementNode(ASTNode el) { shouldPrint(el, _) and not isNotNeeded(el) } or + TParametersNode(Function f) { shouldPrint(f, _) and not isNotNeeded(f) } or + TTypeParametersNode(TypeParameterized f) { shouldPrint(f, _) and not isNotNeeded(f) } or + TJSXAttributesNode(JSXElement n) { shouldPrint(n, _) and not isNotNeeded(n) } or + TJSXBodyElementsNode(JSXNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or + TInvokeArgumentsNode(InvokeExpr n) { shouldPrint(n, _) and not isNotNeeded(n) } or + TInvokeTypeArgumentsNode(InvokeExpr invk) { shouldPrint(invk, _) and not isNotNeeded(invk) } or + // JSON + TJSONNode(JSONValue value) { shouldPrint(value, _) and not isNotNeeded(value) } or + // YAML + TYAMLNode(YAMLNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or + TYAMLMappingNode(YAMLMapping mapping, int i) { + shouldPrint(mapping, _) and not isNotNeeded(mapping) and exists(mapping.getKeyNode(i)) + } or + // HTML + THTMLElementNode(HTML::Element e) { shouldPrint(e, _) and not isNotNeeded(e) } or + THTMLAttributesNodes(HTML::Element e) { shouldPrint(e, _) and not isNotNeeded(e) } or + THTMLAttributeNode(HTML::Attribute attr) { shouldPrint(attr, _) and not isNotNeeded(attr) } or + THTMLScript(Script script) { shouldPrint(script, _) and not isNotNeeded(script) } or + THTMLCodeInAttr(CodeInAttribute attr) { shouldPrint(attr, _) and not isNotNeeded(attr) } + +/** + * A node in the output tree. + */ +class PrintAstNode extends TPrintAstNode { + /** + * Gets a textual representation of this node in the PrintAst output tree. + */ + string toString() { none() } + + /** + * Gets the child node at index `childIndex`. Child indices must be unique, + * but need not be contiguous. + */ + PrintAstNode getChild(int childIndex) { none() } + + /** + * Gets a child of this node. + */ + final PrintAstNode getAChild() { result = getChild(_) } + + /** + * Gets the parent of this node, if any. + */ + final PrintAstNode getParent() { result.getAChild() = this } + + /** + * Gets the location of this node in the source code. + */ + Location getLocation() { none() } + + /** + * Gets the value of the property of this node, where the name of the property + * is `key`. + */ + string getProperty(string key) { + key = "semmle.label" and + result = toString() + } + + /** + * Gets the label for the edge from this node to the specified child. By + * default, this is just the index of the child, but subclasses can override + * this. + */ + string getChildEdgeLabel(int childIndex) { + exists(getChild(childIndex)) and + result = childIndex.toString() + } +} + +/** A top-level AST node. */ +class TopLevelPrintAstNode extends PrintAstNode { + TopLevelPrintAstNode() { not exists(this.getParent()) } + + private int getOrder() { + this = + rank[result](TopLevelPrintAstNode n, Location l | + l = n.getLocation() + | + n + order by + l.getFile().getRelativePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), + l.getEndColumn() + ) + } + + override string getProperty(string key) { + result = super.getProperty(key) + or + key = "semmle.order" and + result = this.getOrder().toString() + } +} + +/** + * Classes for printing JavaScript AST. + */ +private module PrintJavaScript { + /** + * A print node representing an `ASTNode`. + * + * Provides a default implemention that works for some (but not all) ASTNode's. + * More specific subclasses can override this class to get more specific behavior. + * + * The more specific subclasses are mostly used aggregate the children of the `ASTNode`. + * For example by aggregating all the parameters of a function under a single child node. + */ + class ElementNode extends PrintAstNode, TElementNode { + ASTNode element; + + ElementNode() { + this = TElementNode(element) and + not element instanceof Script and // Handled in module `PrintHTML` + not element instanceof CodeInAttribute // Handled in module `PrintHTML` + } + + override string toString() { result = getQlClass(element) + element.toString() } + + override Location getLocation() { result = element.getLocation() } + + /** + * Gets the `ASTNode` represented by this node. + */ + final ASTNode getElement() { result = element } + + override PrintAstNode getChild(int childIndex) { + exists(ASTNode el | result.(ElementNode).getElement() = el | + el = this.getChildNode(childIndex) + ) + } + + /** + * Gets the `i`th child of `element`. + * Can be overriden in subclasses to get more specific behavior for `getChild()`. + */ + ASTNode getChildNode(int childIndex) { result = element.getChild(childIndex) } + } + + /** + * A print node for function invocations. + * + * The children of this node are split into 3. + * 1: The callee. + * 2: An aggregate node for all the arguments. + * 3: An aggregate node for all the type argument. + */ + class InvokeNode extends ElementNode { + override InvokeExpr element; + + override PrintAstNode getChild(int childIndex) { + childIndex = 0 and result.(ElementNode).getElement() = element.getCallee() + or + childIndex = 1 and + exists(element.getAnArgument()) and + result.(InvokeArgumentsNode).getInvokeExpr() = element + or + childIndex = 2 and + exists(element.getATypeArgument()) and + result.(InvokeTypeArgumentsNode).getInvokeExpr() = element + } + } + + /** + * An aggregate node representing all the arguments for an function invocation. + */ + class InvokeArgumentsNode extends PrintAstNode, TInvokeArgumentsNode { + InvokeExpr invk; + + InvokeArgumentsNode() { this = TInvokeArgumentsNode(invk) and exists(invk.getAnArgument()) } + + override string toString() { result = "(Arguments)" } + + /** + * Gets the `InvokeExpr` for which this node represents the arguments. + */ + InvokeExpr getInvokeExpr() { result = invk } + + override PrintAstNode getChild(int childIndex) { + result.(ElementNode).getElement() = invk.getArgument(childIndex) + } + + override Location getLocation() { result = invk.getLocation() } + } + + /** + * An aggregate node representing all the type-arguments for an function invocation. + */ + class InvokeTypeArgumentsNode extends PrintAstNode, TInvokeTypeArgumentsNode { + InvokeExpr invk; + + InvokeTypeArgumentsNode() { + this = TInvokeTypeArgumentsNode(invk) and exists(invk.getATypeArgument()) + } + + override string toString() { result = "(TypeArguments)" } + + /** + * Gets the `InvokeExpr` for which this node represents the type-arguments. + */ + InvokeExpr getInvokeExpr() { result = invk } + + override PrintAstNode getChild(int childIndex) { + result.(ElementNode).getElement() = invk.getTypeArgument(childIndex) + } + + override Location getLocation() { result = invk.getLocation() } + } + + /** + * A print node for JSX nodes. + * + * The children of this node are split into 3. + * 1: The name of the JSX node (for example `Name` in ``). + * 2: An aggregate node for all the attributes (for example `href={foo}` in ``). + * 3: An aggregate node for all the body element (for example `foo` in `foo`). + */ + class JSXNodeNode extends ElementNode { + override JSXNode element; + + override PrintAstNode getChild(int childIndex) { + childIndex = 0 and result.(ElementNode).getElement() = element.(JSXElement).getNameExpr() + or + childIndex = 1 and + exists(element.getABodyElement()) and + result.(JSXBodyElementsNode).getJSXNode() = element + or + childIndex = 2 and + exists(element.(JSXElement).getAttribute(_)) and + result.(JSXAttributesNode).getJSXElement() = element + } + } + + /** + * An aggregate node representing all the attributes in a `JSXNode`. + */ + class JSXAttributesNode extends PrintAstNode, TJSXAttributesNode { + JSXElement n; + + JSXAttributesNode() { this = TJSXAttributesNode(n) and exists(n.getAttribute(_)) } + + override string toString() { result = "(Attributes)" } + + /** + * Gets the `JSXElement` for which this node represents the attributes. + */ + JSXElement getJSXElement() { result = n } + + override PrintAstNode getChild(int childIndex) { + result.(ElementNode).getElement() = n.getAttribute(childIndex) + } + + override Location getLocation() { result = n.getLocation() } + } + + /** + * An aggregate node representing all the body elements in a `JSXNode`. + */ + class JSXBodyElementsNode extends PrintAstNode, TJSXBodyElementsNode { + JSXNode n; + + JSXBodyElementsNode() { this = TJSXBodyElementsNode(n) and exists(n.getBodyElement(_)) } + + override string toString() { result = "(Body)" } + + /** + * Gets the `JSXNode` for which this node represents the body elements. + */ + JSXNode getJSXNode() { result = n } + + override PrintAstNode getChild(int childIndex) { + result.(ElementNode).getElement() = n.getBodyElement(childIndex) + } + + override Location getLocation() { result = n.getLocation() } + } + + /** + * A node representing any `ASTNode` that has type-parameters. + * + * The first child of this node is an aggregate node representing all the type-parameters. + */ + class TypeParameterizedNode extends ElementNode { + override TypeParameterized element; + + override PrintAstNode getChild(int childIndex) { + childIndex = -100 and result.(TypeParametersNode).getTypeParameterized() = element + or + result = super.getChild(childIndex) and + not result.(ElementNode).getElement() = element.getATypeParameter() + } + } + + /** + * A `PrintAstNode` for functions. + * + * The children of this node is split into 6: + * - The identifier (name) of the function. + * - An aggregate node for all the parameters of the function. + * - An aggregate node for all the type parameters of the function. + * - The `this` type annotation. + * - The return type annotation. + * - The body + */ + class FunctionNode extends TypeParameterizedNode { + override Function element; + + override PrintAstNode getChild(int childIndex) { + childIndex = 0 and result.(ElementNode).getElement() = element.getIdentifier() + or + childIndex = 1 and + result.(ParametersNode).getFunction() = element and + exists(element.getAParameter()) + or + childIndex = 2 and + result.(TypeParametersNode).getTypeParameterized() = element and + exists(element.getATypeParameter()) + or + childIndex = 3 and result.(ElementNode).getElement() = element.getThisTypeAnnotation() + or + childIndex = 4 and result.(ElementNode).getElement() = element.getReturnTypeAnnotation() + or + childIndex = 5 and result.(ElementNode).getElement() = element.getBody() + } + } + + /** + * A `PrintAstNode` for parameters. + * + * This node puts the type-annotation and default value of a parameter as children of the parameter itself. + * Instead of the default behavior (from `ElementNode`) where they would be children of the function. + */ + class ParameterNode extends ElementNode { + override Parameter element; + + override ASTNode getChildNode(int childIndex) { + childIndex = 0 and result = element.getTypeAnnotation() + or + childIndex = 1 and result = element.getDefault() + } + } + + /** + * An aggregate node representing all the parameters in a function. + */ + class ParametersNode extends PrintAstNode, TParametersNode { + Function f; + + ParametersNode() { this = TParametersNode(f) and exists(f.getAParameter()) } + + override string toString() { result = "(Parameters)" } + + /** + * Gets the `Function` for which this node represents the parameters. + */ + Function getFunction() { result = f } + + override PrintAstNode getChild(int childIndex) { + result.(ElementNode).getElement() = f.getParameter(childIndex) + } + + override Location getLocation() { result = f.getLocation() } + } + + /** + * An aggregate node representing all the type parameters in a function. + */ + class TypeParametersNode extends PrintAstNode, TTypeParametersNode { + TypeParameterized f; + + TypeParametersNode() { this = TTypeParametersNode(f) and exists(f.getATypeParameter()) } + + override string toString() { result = "(TypeParameters)" } + + /** + * Gets the `Function` for which this node represents the type-parameters. + */ + TypeParameterized getTypeParameterized() { result = f } + + override PrintAstNode getChild(int childIndex) { + result.(ElementNode).getElement() = f.getTypeParameter(childIndex) + } + + override Location getLocation() { result = f.getLocation() } + } +} + +/** + * Classes for printing JSON AST. + */ +private module PrintJSON { + /** + * A print node representing a JSON value in a .json file. + */ + class JSONNode extends PrintAstNode, TJSONNode { + JSONValue value; + + JSONNode() { this = TJSONNode(value) } + + override string toString() { result = getQlClass(value) + value.toString() } + + override Location getLocation() { result = value.getLocation() } + + /** + * Gets the `JSONValue` represented by this node. + */ + final JSONValue getValue() { result = value } + + override PrintAstNode getChild(int childIndex) { + exists(JSONValue child | result.(JSONNode).getValue() = child | + child = value.getChild(childIndex) + ) + } + } +} + +/** + * Classes for printing YAML AST. + */ +module PrintYAML { + /** + * A print node representing a YAML value in a .yml file. + */ + class YAMLNodeNode extends PrintAstNode, TYAMLNode { + YAMLNode node; + + YAMLNodeNode() { this = TYAMLNode(node) } + + override string toString() { result = getQlClass(node) + node.toString() } + + override Location getLocation() { result = node.getLocation() } + + /** + * Gets the `YAMLNode` represented by this node. + */ + final YAMLNode getValue() { result = node } + + override PrintAstNode getChild(int childIndex) { + exists(YAMLNode child | result.(YAMLNodeNode).getValue() = child | + child = node.getChildNode(childIndex) + ) + } + } + + /** + * A print node representing a `YAMLMapping`. + * + * Each child of this node aggregates the key and value of a mapping. + */ + class YAMLMappingNode extends YAMLNodeNode { + override YAMLMapping node; + + override PrintAstNode getChild(int childIndex) { + exists(YAMLMappingMapNode map | map = result | map.maps(node, childIndex)) + } + } + + /** + * A print node representing the `i`th mapping in `mapping`. + */ + class YAMLMappingMapNode extends PrintAstNode, TYAMLMappingNode { + YAMLMapping mapping; + int i; + + YAMLMappingMapNode() { this = TYAMLMappingNode(mapping, i) } + + override string toString() { result = "(Mapping " + i + ")" } + + /** + * Holds if this print node represents the `index`th mapping of `m`. + */ + predicate maps(YAMLMapping m, int index) { + m = mapping and + index = i + } + + override Location getLocation() { result = mapping.getKeyNode(i).getLocation() } + + override PrintAstNode getChild(int childIndex) { + childIndex = 0 and result.(YAMLNodeNode).getValue() = mapping.getKeyNode(i) + or + childIndex = 1 and result.(YAMLNodeNode).getValue() = mapping.getValueNode(i) + } + } +} + +/** + * Classes for printing HTML AST. + */ +module PrintHTML { + /** + * A print node representing an HTML node in a .html file. + */ + class HTMLElementNode extends PrintAstNode, THTMLElementNode { + HTML::Element element; + + HTMLElementNode() { this = THTMLElementNode(element) } + + override string toString() { result = getQlClass(element) + element.toString() } + + override Location getLocation() { result = element.getLocation() } + + /** + * Gets the `HTML::Element` represented by this node. + */ + final HTML::Element getElement() { result = element } + + override PrintAstNode getChild(int childIndex) { + childIndex = -1 and result.(HTMLAttributesNodes).getElement() = element + or + exists(HTML::Element child | result.(HTMLElementNode).getElement() = child | + child = element.(HTML::Element).getChild(childIndex) + ) + } + } + + /** + * A print node representing an HTML node in a .html file. + */ + class HTMLScriptElementNode extends HTMLElementNode { + override HTML::ScriptElement element; + + override PrintAstNode getChild(int childIndex) { + childIndex = -200 and result.(HTMLScript).getScript() = element.getScript() + or + result = super.getChild(childIndex) + } + } + + /** + * A print node representing the code inside a `