Files
codeql/javascript/ql/lib/definitions.qll
2025-06-23 12:54:59 +02:00

215 lines
6.2 KiB
Plaintext

/**
* Provides classes and predicates related to jump-to-definition links
* in the code viewer.
*/
import javascript
import IDEContextual
private import Declarations.Declarations
/**
* Gets the kind of reference that `r` represents.
*
* References in callee position have kind `"M"` (for "method"), all
* others have kind `"V"` (for "variable").
*
* For example, in the expression `f(x)`, `f` has kind `"M"` while
* `x` has kind `"V"`.
*/
private string refKind(RefExpr r) {
if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference())
then result = "M"
else result = "V"
}
/**
* Gets a class, function or object literal `va` may refer to.
*/
private AstNode lookupDef(VarAccess va) {
exists(AbstractValue av | av = va.analyze().getAValue() |
result = av.(AbstractClass).getClass() or
result = av.(AbstractFunction).getFunction() or
result = av.(AbstractObjectLiteral).getObjectExpr()
)
}
/**
* Holds if `va` is of kind `kind` and `def` is the unique class,
* function or object literal it refers to.
*/
private predicate variableDefLookup(VarAccess va, AstNode def, string kind) {
count(lookupDef(va)) = 1 and
def = lookupDef(va) and
kind = refKind(va)
}
/**
* Holds if variable access `va` is of kind `kind` and refers to the
* variable declaration `decl`.
*
* For example, in the statement `var x = 42, y = x;`, the initializing
* expression of `y` is a variable access `x` of kind `"V"` that refers to
* the declaration `x = 42`.
*/
private predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
// restrict to declarations in same file to avoid accidentally picking up
// unrelated global definitions
decl = firstRefInTopLevel(va.getVariable(), Decl(), va.getTopLevel()) and
kind = refKind(va)
}
/**
* Holds if path expression `path`, which appears in a CommonJS `require`
* call or an ES 2015 import statement, imports module `target`; `kind`
* is always "I" (for "import").
*
* For example, in the statement `var a = require("./a")`, the path expression
* `"./a"` imports a module `a` in the same folder.
*/
private predicate importLookup(AstNode path, Module target, string kind) {
kind = "I" and
(
exists(Import i |
path = i.getImportedPathExpr() and
target = i.getImportedModule()
)
or
exists(ReExportDeclaration red |
path = red.getImportedPath() and
target = red.getReExportedModule()
)
)
}
/**
* Gets a node that may write the property read by `prn`.
*/
private AstNode getAWrite(DataFlow::PropRead prn) {
exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName |
base = prn.getBase() and
propName = prn.getPropertyName() and
baseVal = base.getAValue().getAPrototype*()
|
// write to a property on baseVal
exists(AnalyzedPropertyWrite apw |
result = apw.getAstNode() and
apw.writes(baseVal, propName, _)
)
or
// non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled
// separately
exists(ClassDefinition c, MemberDefinition m |
m = c.getMember(propName) and
baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and
result = m.getNameExpr()
)
)
}
/**
* Holds if `prop` is the property name expression of a property read that
* may read the property written by `write`. Furthermore, `write` must be the
* only such property write. Parameter `kind` is always bound to `"M"`
* at the moment.
*/
private predicate propertyLookup(Expr prop, AstNode write, string kind) {
exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() |
count(getAWrite(prn)) = 1 and
write = getAWrite(prn) and
kind = "M"
)
}
/**
* Holds if `ref` is an identifier that refers to a type declared at `decl`.
*/
private predicate typeLookup(AstNode ref, AstNode decl, string kind) {
exists(TypeAccess typeAccess |
ref = typeAccess.getIdentifier() and
decl = typeAccess.getTypeBinding().getTypeDefinition() and
kind = "T"
)
}
/**
* Holds if `ref` is the callee name of an invocation of `decl`.
*/
private predicate typedInvokeLookup(AstNode ref, AstNode decl, string kind) {
not variableDefLookup(ref, decl, _) and
not propertyLookup(ref, decl, _) and
exists(InvokeExpr invoke, Expr callee |
callee = invoke.getCallee().getUnderlyingReference() and
(ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and
decl = invoke.getResolvedCallee() and
kind = "M"
)
}
/**
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
*/
private predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, AstNode decl, string kind) {
decl = ref.getClass().getAstNode() and
kind = "T"
}
private AstNode definitionOfRaw(Locatable e, string kind) {
variableDefLookup(e, result, kind)
or
// prefer definitions over declarations
not variableDefLookup(e, _, _) and variableDeclLookup(e, result, kind)
or
importLookup(e, result, kind)
or
propertyLookup(e, result, kind)
or
typeLookup(e, result, kind)
or
typedInvokeLookup(e, result, kind)
or
jsdocTypeLookup(e, result, kind)
}
/** Gets a more useful node to show for something that resolves to `node`. */
private AstNode redirectOnce(AstNode node) {
exists(ConstructorDeclaration ctor |
ctor.isSynthetic() and
node = ctor.getBody() and
result = ctor.getDeclaringClass()
)
or
exists(ClassDefinition cls |
node = cls and
result = cls.getIdentifier()
)
or
exists(FunctionDeclStmt decl |
node = decl and
result = decl.getIdentifier()
)
or
exists(MethodDeclaration member |
not member instanceof ConstructorDeclaration and
node = member.getBody() and
result = member.getNameExpr()
)
}
private AstNode redirect(AstNode node) {
node = definitionOfRaw(_, _) and
result = redirectOnce*(node) and
not exists(redirectOnce(result))
}
/**
* Gets an element, of kind `kind`, that element `e` uses, if any.
*
* The `kind` is a string representing what kind of use it is:
* - `"M"` for function and method calls
* - `"T"` for uses of types
* - `"V"` for variable accesses
* - `"I"` for imports
*/
cached
AstNode definitionOf(Locatable e, string kind) { result = redirect(definitionOfRaw(e, kind)) }