Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions biz.aQute.bndlib.tests/test/test/ClassParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -497,4 +497,37 @@ public void testNoClassForName() throws Exception {
c.parseClassFile(getClass().getResourceAsStream("classforname/ClassForName.class"));
assertThat(c.getReferred()).doesNotContain(a.getPackageRef("javax.swing"));
}

@Test
public void testProxyNewProxyInstance() throws Exception {
a.setProperty("-noclassforname", "false");
Clazz c = new Clazz(a, "test/proxy", null);
c.parseClassFile(getClass().getResourceAsStream("proxy/ProxyTest.class"));
// TestInterface.getPath() returns Path which is in java.nio.file package
// TestInterface.getList() returns List which is in java.util package
assertThat(c.getReferred()).contains(a.getPackageRef("java.nio.file"));
assertThat(c.getReferred()).contains(a.getPackageRef("java.util"));
}

@Test
public void testNoProxyNewProxyInstance() throws Exception {
a.setProperty("-noclassforname", "true");
Clazz c = new Clazz(a, "test/proxy", null);
c.parseClassFile(getClass().getResourceAsStream("proxy/ProxyTest.class"));
// With -noclassforname, proxy detection should also be disabled
// Note: java.util might still be referenced directly in the code
// but java.nio.file should not be referenced
assertThat(c.getReferred()).doesNotContain(a.getPackageRef("java.nio.file"));
}

@Test
public void testProxyFromFieldNotDetected() throws Exception {
a.setProperty("-noclassforname", "false");
Clazz c = new Clazz(a, "test/proxy", null);
c.parseClassFile(getClass().getResourceAsStream("proxy/ProxyFromField.class"));
// When the Class[] array comes from a field, we cannot reliably detect
// which interfaces are being proxied, so we should NOT add java.nio.file
// The array is created in the static initializer, not inline with newProxyInstance
assertThat(c.getReferred()).doesNotContain(a.getPackageRef("java.nio.file"));
}
}
21 changes: 21 additions & 0 deletions biz.aQute.bndlib.tests/test/test/proxy/ProxyFromField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package test.proxy;

import java.lang.reflect.Proxy;

/**
* Test that we don't incorrectly detect proxy interfaces when the Class[] array
* comes from a static field rather than being created inline.
*/
public class ProxyFromField {

private static final Class<?>[] INTERFACES = new Class<?>[] { TestInterface.class };

public static void main(String[] args) {
TestInterface proxy = (TestInterface) Proxy.newProxyInstance(
ProxyFromField.class.getClassLoader(),
INTERFACES, // Array from field - we cannot detect interfaces reliably
(proxy1, method, args1) -> null
);
System.err.println(proxy);
}
}
Binary file not shown.
16 changes: 16 additions & 0 deletions biz.aQute.bndlib.tests/test/test/proxy/ProxyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package test.proxy;

import java.lang.reflect.Proxy;

public class ProxyTest {

public static void main(String[] args) {
// TestInterface has methods that reference types from java.nio.file and java.util
TestInterface proxy = (TestInterface) Proxy.newProxyInstance(
ProxyTest.class.getClassLoader(),
new Class<?>[] { TestInterface.class },
(proxy1, method, args1) -> null
);
System.err.println(proxy);
}
}
Binary file not shown.
26 changes: 26 additions & 0 deletions biz.aQute.bndlib.tests/test/test/proxy/TestInterface.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package test.proxy;

import java.nio.file.Path;
import java.util.List;

/**
* Test interface that has methods returning types from different packages.
* This is used to test that Proxy.newProxyInstance detection works correctly.
*/
public interface TestInterface {

/**
* Method returning a type from java.nio.file
*/
Path getPath();

/**
* Method returning a type from java.util
*/
List<String> getList();

/**
* Method with parameter from java.nio.file
*/
void setPath(Path path);
}
94 changes: 93 additions & 1 deletion biz.aQute.bndlib/src/aQute/bnd/osgi/Clazz.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
Expand Down Expand Up @@ -783,6 +784,7 @@ public TypeRef getType() {
private Set<TypeRef> annotations;
private int forName = 0;
private int class$ = 0;
private int newProxyInstance = 0;
private Set<PackageRef> api;

private ClassFile classFile = null;
Expand Down Expand Up @@ -916,6 +918,23 @@ private synchronized Set<TypeRef> parseClassFileData(DataInput in) throws Except
: findMethodReference("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
class$ = findMethodReference(classFile.this_class, "class$", "(Ljava/lang/String;)Ljava/lang/Class;");

// We also look for Proxy.newProxyInstance calls to detect dynamic proxy creation:
//
// anewarray #n // class java/lang/Class
// ldc(_w) <class constant> // interface classes
// aastore
// ...
// invokestatic Proxy.newProxyInstance(ClassLoader, Class[], InvocationHandler)
//
// Note: We only detect interfaces when the Class[] array is created inline
// (the anewarray pattern above). We cannot detect interfaces when the array
// comes from a field, local variable, or parameter, as we cannot reliably
// determine the array contents from bytecode alone in those cases.
//
newProxyInstance = analyzer.is(Constants.NOCLASSFORNAME) ? -1
: findMethodReference("java/lang/reflect/Proxy", "newProxyInstance",
"(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;");

for (MethodInfo methodInfo : classFile.methods) {
referTo(methodInfo.descriptor, methodInfo.access);
ElementType elementType = elementType(methodInfo);
Expand Down Expand Up @@ -1240,6 +1259,12 @@ private void processCode(CodeAttribute attribute, ElementType elementType) {
ByteBuffer code = attribute.code.duplicate();
code.rewind();
int lastReference = -1;
// Track interface class constants for Proxy.newProxyInstance
// Note: We only track interfaces when the Class[] array is created inline
// (anewarray + ldc + aastore pattern). We cannot reliably detect interfaces
// when the array comes from a field, variable, or parameter.
List<Integer> proxyInterfaces = null; // Lazy initialization
boolean inProxyArray = false; // Track if we're building a Class[] for proxy
while (code.hasRemaining()) {
int instruction = Byte.toUnsignedInt(code.get());
switch (instruction) {
Expand All @@ -1253,13 +1278,40 @@ private void processCode(CodeAttribute attribute, ElementType elementType) {
classConstRef(lastReference);
break;
}
case OpCodes.anewarray :
case OpCodes.anewarray : {
int class_index = Short.toUnsignedInt(code.getShort());
classConstRef(class_index);
// Check if this is creating a Class[] array (potential Proxy.newProxyInstance pattern)
if (newProxyInstance != -1 && constantPool.tag(class_index) == CONSTANT_Class) {
String className = constantPool.className(class_index);
if ("java/lang/Class".equals(className)) {
inProxyArray = true;
if (proxyInterfaces == null) {
proxyInterfaces = new ArrayList<>();
} else {
proxyInterfaces.clear();
}
}
}
lastReference = -1;
break;
}
case OpCodes.aastore : {
// Store into array - if we're in proxy array building and have a class reference, collect it
if (inProxyArray && lastReference != -1 && constantPool.tag(lastReference) == CONSTANT_Class) {
proxyInterfaces.add(lastReference);
}
lastReference = -1;
break;
}
case OpCodes.checkcast :
case OpCodes.instanceof_ :
case OpCodes.new_ : {
int class_index = Short.toUnsignedInt(code.getShort());
classConstRef(class_index);
lastReference = -1;
// Reset proxy tracking if we see unrelated instructions
inProxyArray = false;
break;
}
case OpCodes.multianewarray : {
Expand All @@ -1280,6 +1332,14 @@ private void processCode(CodeAttribute attribute, ElementType elementType) {
}
}
}
// Handle Proxy.newProxyInstance - process collected proxy interfaces
if (method_ref_index == newProxyInstance && proxyInterfaces != null && !proxyInterfaces.isEmpty()) {
for (int classIndex : proxyInterfaces) {
processProxyInterface(classIndex);
}
proxyInterfaces.clear();
inProxyArray = false;
}
lastReference = -1;
break;
}
Expand Down Expand Up @@ -2088,6 +2148,38 @@ private void classConstRef(String name) {
}
}

/**
* Process a proxy interface - treat it as if the class implements the
* interface, which means we need to reference all types from the
* interface's method signatures (parameters and return types).
*/
private void processProxyInterface(int classIndex) {
String interfaceName = constantPool.className(classIndex);
if (interfaceName == null) {
return;
}

TypeRef interfaceType = analyzer.getTypeRef(interfaceName);
referTo(interfaceType, 0);

// Load the interface class to analyze its methods
try {
Clazz interfaceClazz = analyzer.findClass(interfaceType);
if (interfaceClazz != null) {
// Process all methods in the interface
interfaceClazz.parseClassFile();
interfaceClazz.methods().forEach(method -> {
// Reference all types in the method descriptor (parameters and return type)
String descriptor = method.descriptor();
referTo(descriptor, 0);
});
}
} catch (Exception e) {
// If we can't load the interface, just reference the interface type itself
logger.debug("Unable to load proxy interface {} for detailed analysis: {}", interfaceName, e.getMessage());
}
}

public String getClassSignature() {
return classDef.getSignature();
}
Expand Down