Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Java dataflow configuration - redirecting method calls made to subinterfaces #19665

Discussion options

Hi,

When doing dataflow analysis in Java, is it possible to "redirect" calls made to a method on a subinterface to a specific implementation?

I'm working in a microservices codebase where each service's client has interfaces that are generally organised like this;

package com.acme.foo.client
interface FooService {
 GetFooResponse getFoo(GetFooRequest req);
}
interface FooServiceAsync extends FooService {
 @Override
 default GetFooResponse getFoo(GetFooRequest req) {
 return AsyncHelperLibrary.wrap(getFooAsync(req))
 }
 CompletableFuture<GetFooResponse> getFooAsync(GetFooRequest req);
}
interface FooClient extends FooServiceAsync {}

The actual plumbing/implementation differences between sync and async calls happens at the transport layer (and is not relevant to my analysis), so on the server side only a single implementation exists for getFoo regardless on whether a sync or async call was made

package com.acme.foo.server 
public class FooServer implements FooService {
 @Override
 public GetFooResponse getFoo(GetFooRequest req) {
 //business logic implemented here
 }
}

Dataflow analysis is working out of the box as expected in simple cases where the base interface is used and the corresponding implementation can be found

package com.acme.bar.server 
import com.acme.foo.client.FooService
public class BarServer {
 private final FooService client
 //<cut>
 client.getFoo(req) 
}

However I also want CodeQL to follow the flow in these scenarios where a subinterface is used, and guide it to the implementation found in com.acme.foo.server.FooServer

package com.acme.baz.server
import com.acme.foo.client.FooServiceAsync
public class BazServer {
 private final FooServiceAsync client
 //<cut>
 client.getFoo(req) 
}

And

package com.acme.qux.server
import com.acme.foo.client.FooClient
public class QuxServer {
 private final FooClient client
 //<cut>
 client.getFoo(req) 
}

I've tried (and failed 😭) for a a few hours now to define an isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) predicate that achieves this, but I'm not exactly clear on how to connect the call to the actual implementation I'm after - would someone be able to help or point me towards an example I can borrow from? 🙏

You must be logged in to vote

Essentially is there a way to implement a "jump" step

Yes, you can do that by wiring up individual arguments to the corresponding parameter nodes, but beware that call context will be lost so you lose some precision. Similarly, you'd need to wire up return values to the output of the call. In your snippet above you'd use something like n1.asExpr() = call.getArgument(pos) and n2.asParameter() = impl.getParameter(pos). It's not exactly pretty nor something I'd recommend, but it could maybe work for you.

Replies: 1 comment 2 replies

Comment options

Maybe I'm missing something, so let me see if I got things straight. In BazServer and QuxServer you want the calls to FooServiceAsync::getFoo and FooClient::getFoo to resolve to FooServer::getFoo. But FooServer doesn't implement those subinterfaces, so that doesn't seem possible? Out-of-the-box data flow ought to give you all possible virtual dispatch, but those dispatch targets are ruled out by the Java type system, right?

You must be logged in to vote
2 replies
Comment options

Hi @aschackmull,

Your understanding is correct (and what I'm asking is a bit silly)

Essentially is there a way to implement a "jump" step of some kind in CodeQL to connect those calls to FooServer::getFoo, similar (conceptually at least) to

predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
 exists(MethodCall call, Method impl |
 call.getMethod().hasQualifiedName("com.acme.foo.client, "FooServiceAsync", "getFoo") and
 impl.hasQualifiedName("com.acme.foo.server, "FooServer", "getFoo") and
 call.getMethod().getAnOverride().getAPossibleImplementation() = impl and
 n1.asExpr() = call and
 //n2 as impl?
 )
 }
Comment options

Essentially is there a way to implement a "jump" step

Yes, you can do that by wiring up individual arguments to the corresponding parameter nodes, but beware that call context will be lost so you lose some precision. Similarly, you'd need to wire up return values to the output of the call. In your snippet above you'd use something like n1.asExpr() = call.getArgument(pos) and n2.asParameter() = impl.getParameter(pos). It's not exactly pretty nor something I'd recommend, but it could maybe work for you.

Answer selected by the-cartographer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /