diff --git a/ql/src/codeql_ql/ast/Ast.qll b/ql/src/codeql_ql/ast/Ast.qll index 2f0724abb0f..59343ac2f6a 100644 --- a/ql/src/codeql_ql/ast/Ast.qll +++ b/ql/src/codeql_ql/ast/Ast.qll @@ -232,6 +232,45 @@ class Predicate extends TPredicate, AstNode, Declaration { override string getAPrimaryQlClass() { result = "Predicate" } } +/** + * A relation in the database. + */ +class Relation extends TDBRelation, AstNode, Declaration { + Generated::DbTable table; + + Relation() { this = TDBRelation(table) } + + /** + * Gets the name of the relation. + */ + override string getName() { result = table.getTableName().getChild().getValue() } + + private Generated::DbColumn getColumn(int i) { + result = + rank[i + 1](Generated::DbColumn column, int child | + table.getChild(child) = column + | + column order by child + ) + } + + /** Gets the `i`th parameter name */ + string getParameterName(int i) { result = getColumn(i).getColName().getValue() } + + /** Gets the `i`th parameter type */ + string getParameterType(int i) { + // TODO: This is just using the name of the type, not the actual type. Checkout Type.qll + result = getColumn(i).getColType().getChild().(Generated::Token).getValue() + } + + /** + * Gets the number of parameters. + */ + int getArity() { result = count(getColumn(_)) } + + override string getAPrimaryQlClass() { result = "Relation" } +} + /** * An expression that refers to a predicate, e.g. `BasicBlock::succ/2`. */ @@ -2154,9 +2193,6 @@ module YAML { /** Gets the version of this qlpack */ string getVersion() { result = getProperty("version") } - /** Gets the database scheme of this qlpack */ - string getDbScheme() { result = getProperty("dbscheme") } - /** Gets the extractor of this qlpack */ string getExtractor() { result = getProperty("extractor") } @@ -2190,6 +2226,19 @@ module YAML { ) } + /** Gets the database scheme of this qlpack */ + File getDBScheme() { + result.getBaseName() = getProperty("dbscheme") and + result = file.getParentContainer().getFile(any(string s | s.matches("%.dbscheme"))) + } + + pragma[noinline] + Container getAFileInPack() { + result.getParentContainer() = file.getParentContainer() + or + result = getAFileInPack().(Folder).getAChildContainer() + } + /** * Gets a QLPack that this QLPack depends on. */ diff --git a/ql/src/codeql_ql/ast/internal/AstNodes.qll b/ql/src/codeql_ql/ast/internal/AstNodes.qll index dca2f49c097..d4d0ec10059 100644 --- a/ql/src/codeql_ql/ast/internal/AstNodes.qll +++ b/ql/src/codeql_ql/ast/internal/AstNodes.qll @@ -10,6 +10,7 @@ newtype TAstNode = TClass(Generated::Dataclass dc) or TCharPred(Generated::Charpred pred) or TClassPredicate(Generated::MemberPredicate pred) or + TDBRelation(Generated::DbTable table) or TSelect(Generated::Select sel) or TModule(Generated::Module mod) or TNewType(Generated::Datatype dt) or @@ -153,6 +154,8 @@ Generated::AstNode toGenerated(AST::AstNode n) { or n = TClassPredicate(result) or + n = TDBRelation(result) + or n = TSelect(result) or n = TModule(result) @@ -184,7 +187,7 @@ Generated::AstNode toGenerated(AST::AstNode n) { n = TSuper(result) } -class TPredicate = TCharPred or TClasslessPredicate or TClassPredicate; +class TPredicate = TCharPred or TClasslessPredicate or TClassPredicate or TDBRelation; class TModuleMember = TModuleDeclaration or TImport or TSelect or TQLDoc; diff --git a/ql/src/codeql_ql/ast/internal/Predicate.qll b/ql/src/codeql_ql/ast/internal/Predicate.qll index 5f746237ddc..9279b93a4ea 100644 --- a/ql/src/codeql_ql/ast/internal/Predicate.qll +++ b/ql/src/codeql_ql/ast/internal/Predicate.qll @@ -88,11 +88,37 @@ private module Cached { ) } + pragma[noinline] + private predicate candidate(Relation rel, PredicateCall pc) { + rel.getName() = pc.getPredicateName() + } + + private predicate resolveDBRelation(PredicateCall pc, DefinedPredicate p) { + exists(Relation rel | p = TPred(rel) | + candidate(rel, pc) and + rel.getArity() = pc.getNumberOfArguments() and + ( + exists(YAML::QLPack libPack, YAML::QLPack qlPack | + rel.getLocation().getFile() = libPack.getDBScheme() and + qlPack.getADependency*() = libPack and + qlPack.getAFileInPack() = pc.getLocation().getFile() + ) + or + // upgrade scripts don't have a qlpack + rel.getLocation().getFile().getParentContainer() = + pc.getLocation().getFile().getParentContainer() + ) + ) + } + cached predicate resolveCall(Call c, PredicateOrBuiltin p) { resolvePredicateCall(c, p) or resolveMemberCall(c, p) + or + not resolvePredicateCall(c, _) and + resolveDBRelation(c, p) } cached