Details
Description
Some methods may be bridged by compiler in subclasses since Java 1.6.
Assume the following class hierarchy: a package private Class A and a public class B extends A.
package test;
class A{
public boolean equals(Object o)
}
and
package test;
public class B extends A{
public String getString()
}
(NB: please, don't ask why a package private class is extended by a public class, it doesn't make much sence, but it's a legacy code we have to leave with until a new version is done)
The equals(Object) method is overridden in the class A. Class B does not overrides the equals method. Java compiler creates a brigde equals(Object) method in the class B.
Such bridged equals causes a StackOverflow in EasyMock as soon as we try to mock the class B. The important part of the stack trace:
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:94)
at test.B$$EnhancerByCGLIB$$eb5370b3.equals(<generated>)
at org.easymock.internal.ExpectedInvocation.matches(ExpectedInvocation.java:85)
at org.easymock.internal.UnorderedBehavior.addActual(UnorderedBehavior.java:57)
at org.easymock.internal.MocksBehavior.addActual(MocksBehavior.java:87)
at org.easymock.internal.ReplayState.invokeInner(ReplayState.java:58)
at org.easymock.internal.ReplayState.invoke(ReplayState.java:46)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:40)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:85)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:94)
at test.B$$EnhancerByCGLIB$$eb5370b3.equals(<generated>)
at org.easymock.internal.ExpectedInvocation.matches(ExpectedInvocation.java:85)
at org.easymock.internal.UnorderedBehavior.addActual(UnorderedBehavior.java:57)
at org.easymock.internal.MocksBehavior.addActual(MocksBehavior.java:87)
at org.easymock.internal.ReplayState.invokeInner(ReplayState.java:58)
at org.easymock.internal.ReplayState.invoke(ReplayState.java:46)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:40)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:85)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:94)
at test.B$$EnhancerByCGLIB$$eb5370b3.getString(<generated>)
The problem lies in the ObjectMethodFilter constructor.
Analysis:
Each time an Invocation is compared to another one (when we record a new method call e.g.), the equals() Method of a mock is called. As the equals() of class B is a brigde, the ClassProxyFactory$MockMethodInterceptor.intercept() searches for the bridged method
lines 86-88:
if (method.isBridge())
and finds it - the A.equals(). The A.equals() is then passed to the ObjectMethodsFilter.invoke(). This method tries to check if the method passsed as the "method" argument is an equals method by comparing the attribute with this.equalsMethod. These methods differ, as the "equalsMethod" field is initialized in constructor by calling
equalsMethod = toMock.getMethod("equals", new Class[]
{ Object.class });BUT. As we are mocking the class B, this call will return the bridge method from class B (B.equals), not the bridged method from class A (A.equals) that the invoke() got in the "method" attribute, hence the failed comparison as A.equals != B.equals.
That leads to a call of equals() on the mock, that leads to the comparison of the invocations and so on.
I suppose, a small ObjectMethodFilter constructor code modification like
equalsMethod = BridgeMethodResolver.findBridgedMethod(toMock.getMethod("equals", new Class[] { Object.class }
));
would solve the issue.