Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cde2e34
added MethodHandle test cases
moebarni Jun 22, 2025
732834e
added preanalysis in AbstractCallGraphAlgorithm constructor; added ch…
moebarni Jun 25, 2025
fa59d4a
updated test cases
moebarni Jun 25, 2025
4436c41
added VarHandle test
moebarni Jun 30, 2025
f24ff9a
bad "solution" for RTA
moebarni Jun 30, 2025
eed2df1
Merge branch 'develop' into 1266-feature-investigate-varargs-cg
moebarni Jun 30, 2025
ece4faa
wrapper approach (failed to detect new MethodHandle instance)
moebarni Jul 7, 2025
9c20cf2
extended collectInstantiatedClassesInMethod (only for method/varHandl…
moebarni Jul 11, 2025
9eb4cb8
with comments and separated version
moebarni Jul 11, 2025
5a95bdf
cleaned
moebarni Jul 11, 2025
3aeebd4
Merge branch 'develop' into 1266-feature-investigate-varargs-cg
moebarni Jul 11, 2025
1646566
deleted sout, comment, first test
moebarni Jul 29, 2025
d2a4f11
changed method name; deleted test case for example1; deleted unnecess…
moebarni Jul 29, 2025
1d450b4
deleted sout; added diffParamTest + superClassTest
moebarni Jul 29, 2025
2348c11
Merge branch 'develop' into 1266-feature-investigate-varargs-cg
moebarni Jul 30, 2025
07de6b4
fixed format
moebarni Jul 30, 2025
706fc81
changed preanalysis data structure to Table
moebarni Jul 30, 2025
bc8763d
formatting
moebarni Jul 30, 2025
2988813
Merge branch 'develop' into 1266-feature-investigate-varargs-cg
moebarni Jul 30, 2025
19397b2
deleted duplicate getDeclClassType-method; add methodName-subtype pai…
moebarni Jul 31, 2025
5a00c10
updated methodName-subType pairs
moebarni Aug 1, 2025
5261278
rollback collectInstantiatedClassesInMethod
moebarni Aug 3, 2025
ba19f9b
fmt
moebarni Aug 3, 2025
a8aa9d6
structural changes (getMethodsWithPolymorphicAnnotation)
moebarni Aug 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion sootup.callgraph/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
<artifactId>sootup.jimple.frontend</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.soot-oss</groupId>
<artifactId>sootup.java.core</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
import sootup.core.types.ClassType;
import sootup.core.types.VoidType;
import sootup.core.views.View;
import sootup.java.core.AnnotationUsage;
import sootup.java.core.JavaSootClass;

/**
* The AbstractCallGraphAlgorithm class is the super class of all call graph algorithm. It provides
Expand All @@ -61,10 +63,42 @@ public abstract class AbstractCallGraphAlgorithm implements CallGraphAlgorithm {

@NonNull protected final View view;
@NonNull protected final TypeHierarchy typeHierarchy;
@NonNull protected final List<MethodSignature> preanalysis;
Comment thread
moebarni marked this conversation as resolved.
Outdated

protected AbstractCallGraphAlgorithm(@NonNull View view) {
this.view = view;
this.typeHierarchy = view.getTypeHierarchy();
this.preanalysis = getMethodsWithPolymorphicAnnotation();
}

/**
* Collects all method signatures from the current view that are annotated with {@code
* java.lang.invoke.MethodHandle$PolymorphicSignature}.
*
* @return a list of method signatures annotated with {@code @PolymorphicSignature}
*/
protected List<MethodSignature> getMethodsWithPolymorphicAnnotation() {
List<MethodSignature> polymorphicMethodSigs = new ArrayList<>();
ClassType polymorphicAnnotationType =
view.getIdentifierFactory()
.getClassType("java.lang.invoke.MethodHandle$PolymorphicSignature");
view.getClasses()
.filter(sootClass -> sootClass instanceof JavaSootClass)
.map(sootClass -> (JavaSootClass) sootClass)
.flatMap(javaSootClass -> javaSootClass.getMethods().stream())
.forEach(
javaSootMethod -> {
Iterable<AnnotationUsage> annotationUsages = javaSootMethod.getAnnotations();
if (annotationUsages.equals(Collections.emptyList())) {
return;
}
Comment thread
moebarni marked this conversation as resolved.
Outdated
for (AnnotationUsage annotationUsage : annotationUsages) {
if (annotationUsage.getAnnotation().equals(polymorphicAnnotationType)) {
polymorphicMethodSigs.add(javaSootMethod.getSignature());
Comment thread
moebarni marked this conversation as resolved.
Outdated
}
}
});
return polymorphicMethodSigs;
}

/**
Expand Down Expand Up @@ -592,18 +626,7 @@ protected static Optional<? extends SootMethod> findConcreteMethod(
}

// search method in interfaces
Optional<? extends SootMethod> defaultMethod = findDefaultMethod(view, startClass, methodSig);
if (defaultMethod.isPresent()) {
return defaultMethod;
}

logger.warn(
"Could not find \""
+ sig.getSubSignature()
+ "\" in "
+ sig.getDeclClassType().getClassName()
+ " and in its superclasses and interfaces");
return Optional.empty();
return findDefaultMethod(view, startClass, methodSig);
}

protected static Optional<SootMethod> findMethodInHierarchy(
Expand Down Expand Up @@ -667,4 +690,30 @@ protected static Optional<? extends SootMethod> findDefaultMethod(
protected boolean isInterface(ClassType classType) {
return typeHierarchy.isInterface(classType);
}

/**
* Attempts to resolve a previously unresolved method signature by comparing it with all known
* method signatures annotated with {@code @PolymorphicSignature}. If a match is found (ignoring
* the parameters), the corresponding SootMethod is returned.
*
* @param targetMethodSignature the signature of the searched method
* @return the found method object, or null if the method was not found.
*/
protected Optional<? extends SootMethod> findMatchingPolymorphicMethod(
Comment thread
moebarni marked this conversation as resolved.
Outdated
@NonNull MethodSignature targetMethodSignature) {
for (MethodSignature polymorphicMethodSigs : preanalysis) {
Comment thread
moebarni marked this conversation as resolved.
Outdated
if (targetMethodSignature.getDeclClassType().equals(polymorphicMethodSigs.getDeclClassType())
&& targetMethodSignature.getName().equals(polymorphicMethodSigs.getName())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

two cases are not covered which me might have to investigate.

  1. different parameters,
  2. subclasses of classes that contain these methods

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Created tests.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No subclasses of java.lang.invoke.MethodHandle/VarHandle possible, both classes have package-private constructors and all their implementations are package-private.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can you access the annotation outside of the package?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No: 'java.lang.invoke.MethodHandle.PolymorphicSignature' is not public in 'java.lang.invoke.MethodHandle'. Cannot be accessed from outside package

// return the actualTargetMethodOpt
return view.getMethod(polymorphicMethodSigs).map(sootMethod -> (SootMethod) sootMethod);
Comment thread
moebarni marked this conversation as resolved.
Outdated
}
}
logger.warn(
"Could not find \""
+ targetMethodSignature.getSubSignature()
+ "\" in "
+ targetMethodSignature.getDeclClassType().getClassName()
+ " and in its superclasses and interfaces");
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,14 @@ protected Stream<MethodSignature> resolveCall(SootMethod method, InvokableStmt i
if (actualTargetMethod == null) {
// method not implemented, search for implementation in super classes or ínterfaces
actualTargetMethod = findConcreteMethod(view, targetMethodSignature).orElse(null);
// method implementation isn't contained in the view. return the called method as target
if (actualTargetMethod == null) {
return Stream.of(targetMethodSignature);
// method implementation isn't contained in the view.
// check if method got the PolymorphicSignature annotation
actualTargetMethod = findMatchingPolymorphicMethod(targetMethodSignature).orElse(null);
if (actualTargetMethod == null) {
// method couldn't be resolved, return the called method as target
return Stream.of(targetMethodSignature);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import sootup.core.jimple.common.expr.JSpecialInvokeExpr;
import sootup.core.jimple.common.stmt.InvokableStmt;
import sootup.core.jimple.common.stmt.JAssignStmt;
import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.MethodModifier;
import sootup.core.model.SootClass;
import sootup.core.model.SootMethod;
Expand Down Expand Up @@ -96,13 +97,21 @@ protected List<ClassType> collectInstantiatedClassesInMethod(SootMethod method)
if (method == null || method.isAbstract() || method.isNative()) {
return Collections.emptyList();
}

Set<ClassType> instantiated =
method.getBody().getStmts().stream()
.filter(stmt -> stmt instanceof JAssignStmt)
.filter(Stmt::isJAssignStmt)
.map(stmt -> ((JAssignStmt) stmt).getRightOp())
.filter(value -> value instanceof JNewExpr)
.map(value -> ((JNewExpr) value).getType())
.flatMap(
value -> {
// ClassType as returnType
if (value instanceof AbstractInvokeExpr && value.getType() instanceof ClassType) {
return Stream.of((ClassType) value.getType());
// new-expression
} else if (value instanceof JNewExpr) {
return Stream.of(((JNewExpr) value).getType());
}
return Stream.empty();
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we are only checking for new expressions. Method calls are not considered because first you do not check if they are constructor and second because if you would check for constructors you would consider supertypes as instaniated if the constructor contains a super() call.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Rollback applied. New approach required to treat java.lang.invoke.MethodHandle (with its package-private constructor) as instantiated during RTA analysis.

.collect(Collectors.toSet());
List<ClassType> newInstantiatedClassTypes =
instantiated.stream()
Expand Down Expand Up @@ -141,9 +150,14 @@ protected Stream<MethodSignature> resolveCall(
if (actualTargetMethod == null) {
// method not implemented, search for implementation in super classes or ínterfaces
actualTargetMethod = findConcreteMethod(view, targetMethodSignature).orElse(null);
// method implementation isn't contained in the view. return the called method as target
if (actualTargetMethod == null) {
return Stream.of(targetMethodSignature);
// method implementation isn't contained in the view.
// check if method got the PolymorphicSignature annotation
actualTargetMethod = findMatchingPolymorphicMethod(targetMethodSignature).orElse(null);
if (actualTargetMethod == null) {
// method couldn't be resolved, return the called method as target
return Stream.of(targetMethodSignature);
}
}
}
// special invokes and static invokes can only have one specific target
Expand All @@ -159,7 +173,6 @@ protected Stream<MethodSignature> resolveCall(
.subtypesOf(targetMethodSignature.getDeclClassType())
.flatMap(classType -> view.getClass(classType).stream())
.toList();

// get all targets of these subtypes
Stream<MethodSignature> targets =
resolveAllCallTargets(
Expand Down Expand Up @@ -317,8 +330,9 @@ protected void preProcessingMethod(
if (method == null) {
return;
}

System.out.println("PreProcessed Method: " + method);
Comment thread
moebarni marked this conversation as resolved.
Outdated
List<ClassType> newInstantiatedClasses = collectInstantiatedClassesInMethod(method);
System.out.println("New Instantiated Classes: " + newInstantiatedClasses);
Comment thread
moebarni marked this conversation as resolved.
Outdated
newInstantiatedClasses.forEach(
classType -> includeIgnoredCallsToClass(classType, cg, workList));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import static org.junit.jupiter.api.Assertions.*;

import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
import sootup.core.inputlocation.AnalysisInputLocation;
import sootup.core.jimple.common.Value;
Expand Down Expand Up @@ -41,7 +42,7 @@ public abstract class CallGraphTestBase<T extends AbstractCallGraphAlgorithm> {

private JavaView createViewForClassPath(String classPath) {
List<AnalysisInputLocation> inputLocations = new ArrayList<>();
inputLocations.add(new DefaultRuntimeAnalysisInputLocation());
inputLocations.add(new DefaultRuntimeAnalysisInputLocation()); // SourceType.Application
Comment thread
moebarni marked this conversation as resolved.
Outdated
inputLocations.add(new JavaClassPathAnalysisInputLocation(classPath));

return new JavaView(inputLocations);
Expand Down Expand Up @@ -1265,4 +1266,43 @@ public void testImplicitExample6() {
identifierFactory.getClassType("t6.NoThread"), "run", "void", Collections.emptyList());
assertFalse(cg.containsMethod(runMethodSig));
}

@Test
public void testMethodHandleInvokeExample1() {
CallGraph cg = loadCallGraph("Polymorphic", "e1.MethodHandleInvokeExample1");
MethodSignature invokeMethodSig =
identifierFactory.getMethodSignature(
identifierFactory.getClassType("java.lang.invoke.MethodHandle"),
"invoke",
"java.lang.Object",
Collections.singletonList("java.lang.Object[]"));
Set<MethodSignature> callSourcesMethodSigs = cg.callSourcesTo(invokeMethodSig);
assertTrue(callSourcesMethodSigs.contains(mainMethodSignature));
}

@Test
public void testMethodHandleInvokeExample2() {
CallGraph cg = loadCallGraph("Polymorphic", "e2.MethodHandleInvokeExample2");
MethodSignature invokeMethodSig =
identifierFactory.getMethodSignature(
identifierFactory.getClassType("java.lang.invoke.MethodHandle"),
"invoke",
"java.lang.Object",
Collections.singletonList("java.lang.Object[]"));
Set<MethodSignature> callSourcesMethodSigs = cg.callSourcesTo(invokeMethodSig);
assertTrue(callSourcesMethodSigs.contains(mainMethodSignature));
}

@Test
public void testVarHandleGetExample3() {
CallGraph cg = loadCallGraph("Polymorphic", "e3.VarHandleGetExample3");
MethodSignature getMethodSig =
identifierFactory.getMethodSignature(
identifierFactory.getClassType("java.lang.invoke.VarHandle"),
"get",
"java.lang.Object",
Collections.singletonList("java.lang.Object[]"));
Set<MethodSignature> callSourcesMethodSigs = cg.callSourcesTo(getMethodSig);
assertTrue(callSourcesMethodSigs.contains(mainMethodSignature));
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package e1;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MethodHandleInvokeExample1 {
public static void main(String[] args) throws Throwable {
MethodHandle handle = MethodHandles.lookup()
.findVirtual(String.class, "length", MethodType.methodType(int.class));

handle.invoke("Hello");
}
}
Comment thread
moebarni marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package e2;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MethodHandleInvokeExample2 {
public static void main(String[] args) throws Throwable {
MethodHandle handle = MethodHandles.lookup()
.findVirtual(String.class, "substring", MethodType.methodType(String.class, int.class, int.class));

handle.invoke("HelloWorld", 0, 5);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package e3;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class VarHandleGetExample3 {
static int value = 42;

public static void main(String[] args) throws Throwable {
VarHandle handle = MethodHandles.lookup()
.findStaticVarHandle(VarHandleGetExample3.class, "value", int.class);

handle.get();
}
}