-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Java dataflow configuration - redirecting method calls made to subinterfaces #19665
-
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? 🙏
Beta Was this translation helpful? Give feedback.
All reactions
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
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
-
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?
)
}
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1