Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

Expand Down Expand Up @@ -154,12 +156,22 @@ private void createMethod(Class<?> interfaceClass, List<String> fieldList, List<
Method[] methodAry = interfaceClass.getMethods();
StringBuilder sb = new StringBuilder(512);
int mi = 0;
// Track method signatures to skip duplicate methods from multiple parent interfaces.
// When an interface extends multiple parent interfaces that declare the same method,
// interfaceClass.getMethods() returns duplicates, causing Javassist to throw
// DuplicateMemberException when adding the same method twice.
// See: https://github.com/sofastack/sofa-rpc/issues/1384
Set<String> addedMethodSignatures = new HashSet<String>();
for (Method m : methodAry) {
mi++;
if (Modifier.isNative(m.getModifiers()) || Modifier.isFinal(m.getModifiers()) ||
Modifier.isStatic(m.getModifiers())) {
continue;
}
// Skip duplicate methods inherited from multiple parent interfaces
if (!addedMethodSignatures.add(buildMethodSignature(m))) {
continue;
}
Comment on lines +159 to +173
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

This change adds new behavior (skipping duplicate inherited methods) but there’s no test exercising the failure mode from #1384 (diamond interface inheritance with duplicate method signatures). Since this module already has JavassistProxyTest, consider adding a test interface that extends two parents declaring the same method and assert getProxy() succeeds (and the method invocation is routable) to prevent regressions.

Copilot uses AI. Check for mistakes.
mi++;
Class<?>[] mType = m.getParameterTypes();
Class<?> returnType = m.getReturnType();

Expand Down Expand Up @@ -285,6 +297,28 @@ public Invoker getInvoker(Object proxyObject) {
return parseInvoker(proxyObject);
}

/**
* Builds a unique method signature string in the format: methodName(paramType1,paramType2,...)
* Used to deduplicate methods inherited from multiple parent interfaces that declare the same method.
* Without deduplication, Javassist throws DuplicateMemberException when the same method is added twice.
*
* @param method the method to build a signature for
* @return the method signature string
*/
private String buildMethodSignature(Method method) {
StringBuilder signature = new StringBuilder(method.getName());
signature.append('(');
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (i > 0) {
signature.append(',');
}
signature.append(parameterTypes[i].getName());
}
Comment on lines +308 to +317
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The dedup key in buildMethodSignature() ignores the return type, but JVM/Javassist method uniqueness is based on name + full descriptor (including return type). With covariant return types (or compiler bridge methods), two methods can legally share the same name/parameter types but have different return types; this dedup would incorrectly drop one and may leave the generated proxy missing a required interface method or with the wrong return type. Include the return type in the signature (or derive the JVM descriptor) so only true duplicates are skipped.

Copilot uses AI. Check for mistakes.
signature.append(')');
return signature.toString();
}

/**
* Parse proxy invoker from proxy object
*
Expand Down
Loading