Skip to content
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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

asgerf
Copy link
Contributor

@asgerf asgerf commented Feb 14, 2025

Resolves calls targeting this downwards in the class hierarchy, for example:

class Base {
  foo() {
    this.bar(); // calls Sub.bar
  }
}
class Sub extends Base {
  bar() { ... }
}

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 and viableImplInCallContext.

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.

@github-actions github-actions bot added the JS label Feb 14, 2025
@asgerf asgerf marked this pull request as ready for review February 18, 2025 08:21
@Copilot Copilot bot review requested due to automatic review settings February 18, 2025 08:21
@asgerf asgerf requested a review from a team as a code owner February 18, 2025 08:21
Copy link
Contributor

@Copilot Copilot AI left a 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

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.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines 597 to 600
// Ensure all types are transitively stronger than 'any'
not typeStrongerThan1(t1, _) and
not t1 = TAnyType() and
t2 = TAnyType()
Copy link
Contributor

@erik-krogh erik-krogh Feb 18, 2025

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)

Copy link
Contributor Author

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.

Comment on lines +710 to +714
pragma[nomagic]
private predicate compatibleTypes1(DataFlowType t1, DataFlowType t2) {
t1.asInstanceOfClass().getADirectSubClass+() = t2.asInstanceOfClass()
}

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private predicate downwardCall(DataFlowCall call) {
private predicate mayCallDownwards(DataFlowCall call) {

?

Copy link
Contributor

@erik-krogh erik-krogh left a 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.

@asgerf
Copy link
Contributor Author

asgerf commented Feb 19, 2025

DCA evaluation looks very different now; a regression in vscode and some lost results. Will need to investigate.

@asgerf
Copy link
Contributor Author

asgerf commented Feb 19, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants