Merge pull request #20647 from asgerf/js/type-resolution-cache

JS: Avoid magic and improve a join in type resolution
This commit is contained in:
Asger F
2025-10-20 11:50:23 +02:00
committed by GitHub
2 changed files with 47 additions and 4 deletions

View File

@@ -73,6 +73,7 @@ module NameResolution {
*
* May also include some type-specific steps in cases where this is harmless when tracking values.
*/
pragma[nomagic]
private predicate commonStep(Node node1, Node node2) {
// Import paths are part of the graph and has an incoming edge from the imported module, if found.
// This ensures we can also use the PathExpr as a source when working with external (unresolved) modules.
@@ -187,6 +188,7 @@ module NameResolution {
/**
* Holds if there is a read from `node1` to `node2` that accesses the member `name`.
*/
pragma[nomagic]
predicate readStep(Node node1, string name, Node node2) {
exists(QualifiedTypeAccess access |
node1 = access.getQualifier() and
@@ -321,6 +323,7 @@ module NameResolution {
/**
* Gets the exported member of `mod` named `name`.
*/
pragma[nomagic]
Node getModuleExport(ModuleLike mod, string name) {
exists(ExportDeclaration exprt |
mod = exprt.getContainer() and
@@ -362,6 +365,7 @@ module NameResolution {
* Holds if `value` is stored in `target.prop`. Only needs to recognise assignments
* that are also recognised by JSDoc tooling such as the Closure compiler.
*/
pragma[nomagic]
private predicate storeToVariable(Expr value, string prop, LocalVariableLike target) {
exists(AssignExpr assign |
// target.name = value
@@ -374,6 +378,7 @@ module NameResolution {
}
/** Steps that only apply for this configuration. */
pragma[nomagic]
private predicate specificStep(Node node1, Node node2) {
exists(LexicalName var | S::isRelevantVariable(var) |
node1.(LexicalDecl).getALexicalName() = var and
@@ -406,6 +411,7 @@ module NameResolution {
/** Helps track flow from a particular set of source nodes. */
module Track<nodeSig/1 isSource> {
/** Gets the set of nodes reachable from `source`. */
pragma[nomagic]
Node track(Node source) {
isSource(source) and
result = source
@@ -419,6 +425,7 @@ module NameResolution {
/** Helps track flow from a particular set of source nodes. */
module TrackNode<AstNodeSig Source> {
/** Gets the set of nodes reachable from `source`. */
pragma[nomagic]
Node track(Source source) {
result = source
or
@@ -482,6 +489,7 @@ module NameResolution {
*
* Unlike `trackModule`, this is intended to track uses of external packages.
*/
pragma[nomagic]
predicate nodeRefersToModule(Node node, string mod, string qualifiedName) {
exists(Expr path |
path = any(Import imprt).getImportedPathExpr() or

View File

@@ -12,6 +12,7 @@ module TypeResolution {
* We track through underlying types as an approximate way to handle calls to a type
* that is a union/intersection involving functions.
*/
pragma[nomagic]
Node trackUnderlyingFunctionType(Function fun) {
result = fun
or
@@ -139,6 +140,28 @@ module TypeResolution {
)
}
/**
* `ContentSet.getAReadContent` restricted to the content sets and contents relevant for type resolution.
*/
pragma[nomagic]
private DataFlow::Content getAReadContentRestricted(DataFlow::ContentSet cs) {
valueReadStep(_, cs, _) and
result = cs.getAReadContent() and
typeMember(_, result, _)
}
/**
* `valueReadStep` where the `ContentSet` has been mapped to the set of relevant read-contents.
*/
pragma[nomagic]
private predicate valueReadStepOnContent(Node object, DataFlow::Content content, Node member) {
exists(DataFlow::ContentSet contents |
valueReadStep(object, contents, member) and
content = getAReadContentRestricted(contents)
)
}
pragma[nomagic]
predicate callTarget(InvokeExpr call, Function target) {
exists(ClassDefinition cls |
valueHasType(call.(NewExpr).getCallee(), trackClassValue(cls)) and
@@ -198,6 +221,7 @@ module TypeResolution {
)
}
pragma[nomagic]
predicate contextualType(Node value, Node type) {
exists(LocalVariableLike v |
type = v.getADeclaration().getTypeAnnotation() and
@@ -239,6 +263,7 @@ module TypeResolution {
/**
* Holds if `value` has the given `type`.
*/
cached
predicate valueHasType(Node value, Node type) {
value.(BindingPattern).getTypeAnnotation() = type
or
@@ -293,11 +318,18 @@ module TypeResolution {
or
exists(Node mid | valueHasType(mid, type) | ValueFlow::step(mid, value))
or
exists(Node mid, Node midType, DataFlow::ContentSet contents, Node host |
valueReadStep(mid, contents, value) and
exists(DataFlow::Content content, Node host |
typeMemberHostRead(host, content, value) and
typeMember(host, content, type)
)
}
pragma[nomagic]
private predicate typeMemberHostRead(Node host, DataFlow::Content content, Node target) {
exists(Node mid, Node midType |
valueReadStepOnContent(mid, content, target) and
valueHasType(mid, midType) and
typeMemberHostReaches(host, midType) and
typeMember(host, contents.getAReadContent(), type)
typeMemberHostReaches(host, midType)
)
}
@@ -309,6 +341,7 @@ module TypeResolution {
* - a union type has the property if all its members have the property
*/
module TrackMustProp<nodeSig/1 directlyHasProperty> {
pragma[nomagic]
predicate hasProperty(Node node) {
directlyHasProperty(node)
or
@@ -341,6 +374,7 @@ module TypeResolution {
}
module ValueHasProperty<nodeSig/1 typeHasProperty> {
pragma[nomagic]
predicate valueHasProperty(Node value) {
exists(Node type |
valueHasType(value, type) and
@@ -405,6 +439,7 @@ module TypeResolution {
/**
* Holds if `type` contains `string` or `any`, possibly wrapped in a promise.
*/
pragma[nomagic]
predicate hasUnderlyingStringOrAnyType(Node type) {
type.(TypeAnnotation).isStringy()
or