From ebb1cd3f8f161c6bde82b02a0b28520e57bccc51 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 26 May 2021 14:35:50 +0200 Subject: [PATCH] Module resolution --- ql/consistency-queries/ModuleResolution.ql | 1 + ql/src/codeql_ql/ast/Ast.qll | 46 +++- ql/src/codeql_ql/ast/internal/AstNodes.qll | 7 +- ql/src/codeql_ql/ast/internal/Module.qll | 234 +++++++++++++++++++++ 4 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 ql/consistency-queries/ModuleResolution.ql create mode 100644 ql/src/codeql_ql/ast/internal/Module.qll diff --git a/ql/consistency-queries/ModuleResolution.ql b/ql/consistency-queries/ModuleResolution.ql new file mode 100644 index 00000000000..dbf7b0789f0 --- /dev/null +++ b/ql/consistency-queries/ModuleResolution.ql @@ -0,0 +1 @@ +import codeql_ql.ast.internal.Module::Consistency diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 6af65162fee..65aa4dc3249 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -1,5 +1,6 @@ import ql private import codeql_ql.ast.internal.AstNodes +private import codeql_ql.ast.internal.Module /** An AST node of a QL program */ class AstNode extends TAstNode { @@ -45,6 +46,21 @@ class Predicate extends TPredicate, AstNode { // TODO: ReturnType. } +class PredicateExpr extends TPredicateExpr, AstNode { + Generated::PredicateExpr pe; + + PredicateExpr() { this = TPredicateExpr(pe) } + + override string toString() { result = "predicate" } + + ModuleExpr getQualifier() { + exists(Generated::AritylessPredicateExpr ape | + ape.getParent() = pe and + toGenerated(result).getParent() = ape + ) + } +} + /** * A classless predicate. */ @@ -54,6 +70,13 @@ class ClasslessPredicate extends TClasslessPredicate, Predicate, ModuleMember { ClasslessPredicate() { this = TClasslessPredicate(member, pred) } + final PredicateExpr getAlias() { + exists(Generated::PredicateAliasBody alias | + alias.getParent() = pred and + toGenerated(result).getParent() = alias + ) + } + final override predicate isPrivate() { member.getAFieldOrChild().(Generated::Annotation).getName().getValue() = "private" } @@ -340,6 +363,8 @@ class Call extends TCall, Expr { Expr getArgument(int i) { none() // overriden in sublcasses. } + + ModuleExpr getQualifier() { none() } } class PredicateCall extends TPredicateCall, Call { @@ -353,6 +378,13 @@ class PredicateCall extends TPredicateCall, Call { ) } + final override ModuleExpr getQualifier() { + exists(Generated::AritylessPredicateExpr ape | + ape.getParent() = expr and + toGenerated(result).getParent() = ape + ) + } + override string getAPrimaryQlClass() { result = "PredicateCall" } string getPredicateName() { @@ -411,10 +443,16 @@ class InlineCast extends TInlineCast, Expr { Expr getBase() { toGenerated(result) = expr.getChild(0) } } +/** An entity that resolves to a module. */ +class ModuleRef extends AstNode, TModuleRef { + /** Gets the module that this entity resolves to. */ + FileOrModule getResolvedModule() { none() } +} + /** * An import statement. */ -class Import extends TImport, ModuleMember { +class Import extends TImport, ModuleMember, ModuleRef { Generated::ImportDirective imp; Import() { this = TImport(imp) } @@ -456,6 +494,8 @@ class Import extends TImport, ModuleMember { member.getAFieldOrChild().(Generated::Annotation).getName().getValue() = "private" ) } + + final override FileOrModule getResolvedModule() { resolve(this, result) } } /** A formula, such as `x = 6 and y < 5`. */ @@ -813,7 +853,7 @@ class DontCare extends TDontCare, Expr { } /** A module expression. */ -class ModuleExpr extends TModuleExpr, AstNode { +class ModuleExpr extends TModuleExpr, ModuleRef { Generated::ModuleExpr me; ModuleExpr() { this = TModuleExpr(me) } @@ -844,5 +884,7 @@ class ModuleExpr extends TModuleExpr, AstNode { */ ModuleExpr getQualifier() { result = TModuleExpr(me.getChild()) } + final override FileOrModule getResolvedModule() { resolveModuleExpr(this, result) } + final override string toString() { result = this.getName() } } diff --git a/ql/src/codeql_ql/ast/internal/AstNodes.qll b/ql/src/codeql_ql/ast/internal/AstNodes.qll index c90231b9d0c..5e29cb89967 100644 --- a/ql/src/codeql_ql/ast/internal/AstNodes.qll +++ b/ql/src/codeql_ql/ast/internal/AstNodes.qll @@ -44,7 +44,8 @@ newtype TAstNode = TLiteral(Generated::Literal lit) or TUnaryExpr(Generated::UnaryExpr unaryexpr) or TDontCare(Generated::Underscore dontcare) or - TModuleExpr(Generated::ModuleExpr me) + TModuleExpr(Generated::ModuleExpr me) or + TPredicateExpr(Generated::PredicateExpr pe) class TFormula = TDisjunction or TConjunction or TComparisonFormula or TQuantifier or TNegation or TIfFormula or @@ -58,6 +59,8 @@ class TExpr = class TCall = TPredicateCall or TMemberCall or TNoneCall or TAnyCall; +class TModuleRef = TImport or TModuleExpr; + Generated::AstNode toGeneratedFormula(AST::AstNode n) { n = TConjunction(result) or n = TDisjunction(result) or @@ -115,6 +118,8 @@ Generated::AstNode toGenerated(AST::AstNode n) { or n = TModuleExpr(result) or + n = TPredicateExpr(result) + or n = TPredicateCall(result) or n = TMemberCall(result) diff --git a/ql/src/codeql_ql/ast/internal/Module.qll b/ql/src/codeql_ql/ast/internal/Module.qll new file mode 100644 index 00000000000..cf87d268e6c --- /dev/null +++ b/ql/src/codeql_ql/ast/internal/Module.qll @@ -0,0 +1,234 @@ +import ql +private import codeql_ql.ast.internal.AstNodes as AstNodes +private import codeql_ql.ast.internal.TreeSitter + +private newtype TContainerOrModule = + TFile(File f) or + TFolder(Folder f) or + TModule(Module m) + +private class ContainerOrModule extends TContainerOrModule { + string getName() { none() } + + ContainerOrModule getEnclosing() { none() } + + string toString() { none() } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + none() + } +} + +private class TFileOrModule = TFile or TModule; + +/** A file or a module. */ +class FileOrModule extends TFileOrModule, ContainerOrModule { } + +private class File_ extends FileOrModule, TFile { + File f; + + File_() { this = TFile(f) } + + override ContainerOrModule getEnclosing() { result = TFolder(f.getParentContainer()) } + + override string getName() { result = f.getStem() } + + override string toString() { result = f.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = f.getAbsolutePath() and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } +} + +private class Folder_ extends ContainerOrModule, TFolder { + Folder f; + + Folder_() { this = TFolder(f) } + + override ContainerOrModule getEnclosing() { result = TFolder(f.getParentContainer()) } + + override string getName() { result = f.getStem() } + + override string toString() { result = f.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = f.getAbsolutePath() and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } +} + +// TODO: Use `AstNode::getParent` once it is total +private Generated::AstNode parent(Generated::AstNode n) { + result = n.getParent() and + not n instanceof Generated::Module +} + +private Module getEnclosingModule0(AstNode n) { + AstNodes::toGenerated(result) = parent*(AstNodes::toGenerated(n).getParent()) +} + +private ContainerOrModule getEnclosingModule(AstNode n) { + result = TModule(getEnclosingModule0(n)) + or + not exists(getEnclosingModule0(n)) and + result = TFile(n.getLocation().getFile()) +} + +class Module_ extends FileOrModule, TModule { + Module m; + + Module_() { this = TModule(m) } + + override ContainerOrModule getEnclosing() { result = getEnclosingModule(m) } + + override string getName() { result = m.getName() } + + override string toString() { result = m.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + m.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) { + not m = TFile(any(File f | f.getExtension() = "ql")) and + exists(string q | q = imp.getQualifiedName(i) | + i = 0 and + ( + exists(Container c, Container parent | + // should ideally look at `qlpack.yml` files + parent = imp.getLocation().getFile().getParentContainer+() and + c.getParentContainer() = parent and + q = m.getName() + | + m = TFile(c) + or + m = TFolder(c) + ) + or + defines(getEnclosingModule(imp).getEnclosing*(), q, m, _) + ) + or + exists(Folder_ mid | + resolveQualifiedName(imp, mid, i - 1) and + m.getEnclosing() = mid and + q = m.getName() + ) + ) +} + +private predicate resolveSelectionName(Import imp, ContainerOrModule m, int i) { + exists(int last | + resolveQualifiedName(imp, m, last) and + last = count(int j | exists(imp.getQualifiedName(j))) - 1 + ) and + not m instanceof Folder_ and + i = -1 + or + exists(ContainerOrModule mid | + resolveSelectionName(imp, mid, i - 1) and + defines(mid, imp.getSelectionName(i), m, true) + ) +} + +/** Holds if import statement `imp` resolves to `m`. */ +predicate resolve(Import imp, FileOrModule m) { + exists(int last | + resolveSelectionName(imp, m, last) and + last = count(int j | exists(imp.getSelectionName(j))) - 1 + ) +} + +/** Holds if module expression `me` resolves to `m`. */ +predicate resolveModuleExpr(ModuleExpr me, FileOrModule m) { + not m = TFile(any(File f | f.getExtension() = "ql")) and + not exists(me.getQualifier()) and + defines(getEnclosingModule(me).getEnclosing*(), me.getName(), m, _) + or + exists(FileOrModule mid | + resolveModuleExpr(me.getQualifier(), mid) and + defines(mid, me.getName(), m, true) + ) +} + +private boolean getPublicBool(ModuleMember m) { + if m.isPrivate() then result = false else result = true +} + +/** Holds if `container` defines module `m` with name `name`. */ +private predicate defines( + ContainerOrModule container, string name, ContainerOrModule m, boolean public +) { + container = m.getEnclosing() and + name = m.getName() and + ( + (m instanceof File_ or m instanceof Folder_) and + public = true + or + m = TModule(any(Module mod | public = getPublicBool(mod))) + ) + or + // import X + exists(Import imp, ContainerOrModule m0 | + container = getEnclosingModule(imp) and + resolve(imp, m0) and + not exists(imp.importedAs()) and + defines(m0, name, m, true) and + public = getPublicBool(imp) + ) + or + // import X as Y + exists(Import imp | + container = getEnclosingModule(imp) and + name = imp.importedAs() and + resolve(imp, m) and + public = getPublicBool(imp) + ) + or + // module X = Y + exists(Module alias | + container = getEnclosingModule(alias) and + name = alias.getName() and + resolveModuleExpr(alias.getAlias(), m) and + public = getPublicBool(alias) + ) +} + +module Consistency { + query predicate noResolve(Import imp) { + not resolve(imp, _) and + not imp.getLocation().getFile().getAbsolutePath().regexpMatch(".*/(test|examples)/.*") + } + + query predicate noResolveModuleExpr(ModuleExpr me) { + not resolveModuleExpr(me, _) and + not me.getLocation().getFile().getAbsolutePath().regexpMatch(".*/(test|examples)/.*") + } + + query predicate multipleResolve(Import imp, int c, ContainerOrModule m) { + c = strictcount(ContainerOrModule m0 | resolve(imp, m0)) and + c > 1 and + resolve(imp, m) + } + + query predicate multipleResolveModuleExpr(ModuleExpr me, int c, ContainerOrModule m) { + c = strictcount(ContainerOrModule m0 | resolveModuleExpr(me, m0)) and + c > 1 and + resolveModuleExpr(me, m) + } +}