diff --git a/ql/src/go.qll b/ql/src/go.qll index b7d8af964a1..d2dc498b6f2 100644 --- a/ql/src/go.qll +++ b/ql/src/go.qll @@ -9,6 +9,7 @@ import semmle.go.Concepts import semmle.go.Decls import semmle.go.Expr import semmle.go.Files +import semmle.go.GoModExpr import semmle.go.Locations import semmle.go.Packages import semmle.go.Scopes diff --git a/ql/src/semmle/go/AST.qll b/ql/src/semmle/go/AST.qll index 3d1dddf8981..a58a8c3d78a 100644 --- a/ql/src/semmle/go/AST.qll +++ b/ql/src/semmle/go/AST.qll @@ -16,6 +16,7 @@ class AstNode extends @node, Locatable { */ AstNode getChild(int i) { result = this.(ExprParent).getChildExpr(i) or + result = this.(GoModExprParent).getChildGoModExpr(i) or result = this.(StmtParent).getChildStmt(i) or result = this.(DeclParent).getDecl(i) or result = this.(GenDecl).getSpec(i) or @@ -70,6 +71,29 @@ class ExprParent extends @exprparent, AstNode { int getNumChildExpr() { result = count(getAChildExpr()) } } +/** + * An AST node whose children include go.mod expressions. + */ +class GoModExprParent extends @modexprparent, AstNode { + /** + * Gets the `i`th child expression of this node. + * + * Note that the precise indices of child expressions are considered an implementation detail + * and are subject to change without notice. + */ + GoModExpr getChildGoModExpr(int i) { modexprs(result, _, this, i) } + + /** + * Gets an expression that is a child node of this node in the AST. + */ + GoModExpr getAChildGoModExpr() { result = getChildGoModExpr(_) } + + /** + * Gets the number of child expressions of this node. + */ + int getNumChildGoModExpr() { result = count(getAChildGoModExpr()) } +} + /** * An AST node whose children include statements. */ diff --git a/ql/src/semmle/go/Files.qll b/ql/src/semmle/go/Files.qll index 963c9943106..170ba9c2b3d 100644 --- a/ql/src/semmle/go/Files.qll +++ b/ql/src/semmle/go/Files.qll @@ -178,7 +178,8 @@ class Folder extends Container, @folder { } /** A file. */ -class File extends Container, @file, Documentable, ExprParent, DeclParent, ScopeNode { +class File extends Container, @file, Documentable, ExprParent, GoModExprParent, DeclParent, + ScopeNode { override Location getLocation() { has_location(this, result) } override string getAbsolutePath() { files(this, result, _, _, _) } diff --git a/ql/src/semmle/go/GoModExpr.qll b/ql/src/semmle/go/GoModExpr.qll new file mode 100644 index 00000000000..fced69266f7 --- /dev/null +++ b/ql/src/semmle/go/GoModExpr.qll @@ -0,0 +1,167 @@ +/** + * Provides classes for working with go.mod expressions. + */ + +import go + +/** + * A go.mod expression. + */ +class GoModExpr extends @modexpr, GoModExprParent { + /** + * Gets the kind of this expression, which is an integer value representing the expression's + * node type. + * + * Note that the mapping from node types to integer kinds is considered an implementation detail + * and subject to change without notice. + */ + int getKind() { modexprs(this, result, _, _) } + + /** + * Get the comment group associated with this expression. + */ + DocComment getComments() { result.getDocumentedElement() = this } + + override string toString() { result = "go.mod expression" } +} + +/** + * A top-level block of comments separate from any rule. + */ +class GoModCommentBlock extends @modcommentblock, GoModExpr { } + +/** + * A single line of tokens. + */ +class GoModLine extends @modline, GoModExpr { + /** + * Gets the `i`th token on this line. + */ + string getToken(int i) { modtokens(result, this, i) } + + override string toString() { result = "go.mod line" } +} + +/** + * A line that contains the module information + */ +class GoModModuleLine extends GoModLine { + GoModModuleLine() { + this.getParent().(GoModLineBlock).getToken(0) = "module" + or + not this.getParent() instanceof GoModLineBlock and + this.getToken(0) = "module" + } + + string getPath() { + if this.getParent() instanceof GoModLineBlock + then result = this.getToken(1) + else result = this.getToken(0) + } + + override string toString() { result = "go.mod module line" } +} + +/** + * A factored block of lines, for example `require ( "path" )`. + */ +class GoModLineBlock extends @modlineblock, GoModExpr { + /** + * Gets the `i`th token of this line block. + */ + string getToken(int i) { modtokens(result, this, i) } + + override string toString() { result = "go.mod line block" } +} + +/** + * A line that declares the Go version to be used, for example `go 1.14`. + */ +class GoModGoLine extends GoModLine { + GoModGoLine() { this.getToken(0) = "go" } + + /** Gets the Go version declared. */ + string getVer() { result = this.getToken(1) } + + override string toString() { result = "go.mod go line" } +} + +private string getOffsetToken(GoModLine line, int i) { + if line.getParent() instanceof GoModLineBlock + then result = line.getToken(i - 1) + else result = line.getToken(i) +} + +/** + * A line that declares a requirement, for example `require "path"`. + */ +class GoModRequireLine extends GoModLine { + GoModRequireLine() { + this.getParent().(GoModLineBlock).getToken(0) = "require" + or + not this.getParent() instanceof GoModLineBlock and + this.getToken(0) = "require" + } + + /** Gets the path of the dependency. */ + string getPath() { result = getOffsetToken(this, 1) } + + /** Gets the version of the dependency. */ + string getVer() { result = getOffsetToken(this, 2) } + + override string toString() { result = "go.mod require line" } +} + +/** + * A line that declares a dependency version to exclude, for example `exclude "ver"`. + */ +class GoModExcludeLine extends GoModLine { + GoModExcludeLine() { + this.getParent().(GoModLineBlock).getToken(0) = "exclude" + or + not this.getParent() instanceof GoModLineBlock and + this.getToken(0) = "exclude" + } + + /** Gets the path of the dependency to exclude a version of. */ + string getPath() { result = getOffsetToken(this, 1) } + + /** Gets the excluded version. */ + string getVer() { result = getOffsetToken(this, 2) } + + override string toString() { result = "go.mod exclude line" } +} + +/** + * A line that specifies a dependency to use instead of another one, for example + * `replace "a" => "b" ver`. + */ +class GoModReplaceLine extends GoModLine { + GoModReplaceLine() { + this.getParent().(GoModLineBlock).getToken(0) = "replace" + or + not this.getParent() instanceof GoModLineBlock and + this.getToken(0) = "replace" + } + + /** Gets the path of the dependency to be replaced. */ + string getOriginalPath() { result = getOffsetToken(this, 1) } + + /** Gets the path of the replacement dependency. */ + string getReplacementPath() { result = getOffsetToken(this, 3) } + + /** Gets the version of the replacement dependency. */ + string getReplacementVer() { result = getOffsetToken(this, 4) } + + override string toString() { result = "go.mod replace line" } +} + +/** A left parenthesis for a line block. */ +class GoModLParen extends @modlparen, GoModExpr { + override string toString() { result = "go.mod (" } +} + +/** A right parenthesis for a line block. */ +class GoModRParen extends @modrparen, GoModExpr { + override string toString() { result = "go.mod )" } +}