-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JS: Resolve calls downward in class hierarchy #18783
base: main
Are you sure you want to change the base?
Conversation
Previously it would only report a spurious callee if the target function was named. Now, if specifying 'calls:NONE' if will report any callee as spurious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Overview
This PR improves the call resolution logic to handle downward calls in the class hierarchy, ensuring that calls to methods on "this" are resolved to subclass implementations when appropriate. Key changes include:
- Addition of test cases in TripleDot to validate downward call resolution.
- Annotated test updates in CallGraphs to reflect calls resolving into subclass methods.
- A new change note documenting the improvements in call resolution.
- A minor update in accessors tests to refactor object creation.
Changes
File | Description |
---|---|
javascript/ql/test/library-tests/TripleDot/subclass.js | Added test cases for downward call resolution in subclasses. |
javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/subclasses.js | Updated annotated tests with downward call resolution and added tests for method overriding. |
javascript/ql/src/change-notes/2025-02-17-downward-calls.md | Documented the improvements to downward call resolution and data flow handling. |
javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/accessors.js | Adjusted the test case to use a stored object reference instead of an inline instantiation. |
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Tip: Copilot code review supports C#, Go, Java, JavaScript, Markdown, Python, Ruby and TypeScript, with more languages coming soon. Learn more
javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/subclasses.js
Outdated
Show resolved
Hide resolved
or | ||
result = this.getADirectSuperClass().getAnInstanceMemberAccessOnSuperClass(name) | ||
} | ||
|
||
/** | ||
* Gets a property read that accesses the property `name` on an instance of this class. | ||
* | ||
* Concretely, this holds when the base is an instance of this class or a subclass thereof. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This docstring is outdated.
It's either:
- A (transitive) subclass, where the subclass doesn't have a write to that property.
- A superclass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the doc.
where the subclass doesn't have a write to that property.
It may have a write, just not a member declaration or a function stored on this
in the constructor.
// Ensure all types are transitively stronger than 'any' | ||
not typeStrongerThan1(t1, _) and | ||
not t1 = TAnyType() and | ||
t2 = TAnyType() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me, assuming that the shared dataflow library handles the transitive closure.
Similarly the typeStrongerThan1
predicate feels like it needs a transitive closure.
So maybe add a comment there that the shared dataflow handles the transitive closure. (If that's the case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a closer look the shared library actually doesn't do the transitive closure. Good thing you mentioned this.
Updating this to do the transitive closure. Will need another evaluation now.
pragma[nomagic] | ||
private predicate compatibleTypes1(DataFlowType t1, DataFlowType t2) { | ||
t1.asInstanceOfClass().getADirectSubClass+() = t2.asInstanceOfClass() | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Transitive closure is not automatically handled by the shared dataflow library for the compatibleTypes
predicate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type-compatibility is not transitive. If Base
is compatible with Subclass1
and Subclass2
that doesn't mean Subclass1
and Subclass2
are compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right.
LGTM 👍
result.asPartialCall().getACallbackNode() = cls.getAReceiverNode().getAPropertyRead() | ||
} | ||
|
||
private predicate downwardCall(DataFlowCall call) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private predicate downwardCall(DataFlowCall call) { | |
private predicate mayCallDownwards(DataFlowCall call) { |
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, assuming the evaluation is good.
DCA evaluation looks very different now; a regression in vscode and some lost results. Will need to investigate. |
Actually vscode failed in the initial DCA run, it just wasn't obvious enough for me to notice apparently. It failed during the TaintedNodes meta-query which wasn't run in the second run as meta queries were turned off. |
Resolves calls targeting
this
downwards in the class hierarchy, for example:We already resolve calls upwards in the class hierarchy. One of the risks from resolving both upwards and downwards is that flow can start in one subclass, go upwards to a base class, and then move into a different subclass. This PR mitigates using some features of the shared data flow library involving
DataFlowType
andviableImplInCallContext
.Both DCA and QA run were very quiet (see backlinks). The DCA run showed 531 new call edges across 95 projects, resulting in 6 new sinks and 694 nodes now being tainted.