Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java 21 cause StackOverFlowException in TypeUtils#resolveGenerics due to List implementing SequencedCollection with new function getFirst() being treated as a getter #9855

Closed
bubbleguuum opened this issue Sep 25, 2023 · 4 comments · Fixed by #9858

Comments

@bubbleguuum
Copy link

bubbleguuum commented Sep 25, 2023

GWT version: 2.8.2
Browser (with version): Firefox (does not matter)
Operating System: Linux (does not matter)


Description

When testing my existing program using older GWT 2.8.2 under Java 21, I had the surprise to see it crash on a StackOverFlowException when connecting to it with a web browser. See stack trace at the end of this message.

TypeUtils#resolveGenerics is called recursively indefinitely, causing the StackOverflow.

Since it never happened on previous Java versions, I compared in a debugger between Java 17 and Java 21.

The problematic TypeUtils#resolveGenerics(Class<?> containingType, Type type) call is originating from ProxyAutoBean#calculateData with this line:

Type genericReturnType = TypeUtils.resolveGenerics(beanType, method.getGenericReturnType());

The invocation that cause the stack overflow is with beanType being Class<java.util.List>, method's name is "getFirst" (the newly added function from the SequencedCollection interface inherited by List) and method.getGenericReturnType() being of type TypeVariableImpl which is causing this block in TypeUtils#resolveGenerics to be called recursively indefinitely:

...
} else if (type instanceof TypeVariable<?>) {
      TypeVariable<?> variable = (TypeVariable<?>) type;
      Object genericDeclaration = variable.getGenericDeclaration();
      if (genericDeclaration instanceof Class<?>) {
        Class<?> declaration = (Class<?>) genericDeclaration;
        // could probably optimize, but would involve duplicating a bunch of
        // getParameterization's code
        Type[] types = getParameterization(declaration, containingType);
        TypeVariable<?>[] x = declaration.getTypeParameters();
        for (int i = 0; i < x.length; i++) {
          if (variable.equals(x[i])) {
            return resolveGenerics(containingType, types[i]);                       //// INFINITE RECURSION HERE
          }
        }
        throw new IllegalStateException(
            "TypeVariable not found in its generic declaration type; must be a JVM error");
      } else {
        // Use "erasure" for method-level type variables; should we throw
        // instead?
        return ensureBaseType(type);
      }
    }
...

Note that prior to Java 21, my program never triggered the execution of this block code above.
It is specifically triggered by the new getFirst/getLast methods of List in Java 21.

So there is a bug with this code (not working properly or not guarded for infinite recursion) or the calling function ProxyAutoBean#calculateData should exclude getFirst() and getLast() introduced in the SequencedCollection interface inherited by List and other collections.

The code in com/google/web/bindery/autobean/vm/impl has not been modified in 9 years, so I bet this issue still exist in current GWT version.

Steps to reproduce

Run a GWT program under Java 21 that results in ProxyAutoBean#calculateData being invoked with Class<java.util.list> as parameter.

////////////////////

java.lang.RuntimeException: java.lang.StackOverflowError
	at com.google.web.bindery.autobean.vm.impl.ProxyAutoBean.traverseProperties(ProxyAutoBean.java:328)
	at com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.traverse(AbstractAutoBean.java:166)
	at com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.accept(AbstractAutoBean.java:101)
	at com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.doCoderFor(AutoBeanCodexImpl.java:522)
	at com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.getOrReify(AbstractAutoBean.java:239)
	at com.google.web.bindery.autobean.vm.impl.ProxyAutoBean.getOrReify(ProxyAutoBean.java:282)
	at com.google.web.bindery.autobean.vm.impl.BeanMethod$2.invoke(BeanMethod.java:73)
	at com.google.web.bindery.autobean.vm.impl.SimpleBeanHandler.invoke(SimpleBeanHandler.java:43)
	at jdk.proxy2/jdk.proxy2.$Proxy49.getRequestFactory(Unknown Source)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at com.google.web.bindery.autobean.vm.impl.ShimHandler.invoke(ShimHandler.java:78)
	at jdk.proxy2/jdk.proxy2.$Proxy49.getRequestFactory(Unknown Source)
	at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.process(SimpleRequestProcessor.java:210)
	at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.process(SimpleRequestProcessor.java:135)
	at com.google.web.bindery.requestfactory.server.RequestFactoryServlet.doPost(RequestFactoryServlet.java:135)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:652)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1317)
	at com.bubblesoft.bubbleupnpserver.server.servlets.AllowWANConnectionsFilter.doFilter(AllowWANConnectionsFilter.java:34)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1288)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:443)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:574)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:227)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1044)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:372)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:189)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:978)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
	at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:154)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
	at org.eclipse.jetty.server.Server.handle(Server.java:369)
	at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:464)
	at org.eclipse.jetty.server.BlockingHttpConnection.handleRequest(BlockingHttpConnection.java:53)
	at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:924)
	at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:985)
	at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:861)
	at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:236)
	at org.eclipse.jetty.server.BlockingHttpConnection.handle(BlockingHttpConnection.java:72)
	at org.eclipse.jetty.server.bio.SocketConnector$ConnectorEndPoint.run(SocketConnector.java:264)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
	at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.StackOverflowError
	at java.base/java.lang.Class.getTypeParameters(Class.java:1047)
	at com.google.web.bindery.autobean.vm.impl.TypeUtils.getParameterization(TypeUtils.java:135)
	at com.google.web.bindery.autobean.vm.impl.TypeUtils.resolveGenerics(TypeUtils.java:212)
	at com.google.web.bindery.autobean.vm.impl.TypeUtils.resolveGenerics(TypeUtils.java:216)
	at com.google.web.bindery.autobean.vm.impl.TypeUtils.resolveGenerics(TypeUtils.java:216)
	at com.google.web.bindery.autobean.vm.impl.TypeUtils.resolveGenerics(TypeUtils.java:216)
	at com.google.web.bindery.autobean.vm.impl.TypeUtils.resolveGenerics(TypeUtils.java:216)
	at com.google.web.bindery.autobean.vm.impl.TypeUtils.resolveGenerics(TypeUtils.java:216)
        ............
@bubbleguuum bubbleguuum changed the title Java 21 cause StackOverFlowException in TypeUtils#resolveGenerics due to List implementing SequencedCollection with new function setFirst() being treated as a setter Java 21 cause StackOverFlowException in TypeUtils#resolveGenerics due to List implementing SequencedCollection with new function getFirst() being treated as a getter Sep 25, 2023
@bubbleguuum
Copy link
Author

bubbleguuum commented Sep 25, 2023

This can be trivially reproduced running this line on Java 21:

new ProxyAutoBean(null, List.class, null);

Unsurprisingly it also happens with any class implementing SequencedCollection.

@dodgex
Copy link
Contributor

dodgex commented Oct 21, 2023

With this snipped it is even reproducible on JDKs before 21 (tested on 18, but should be the same on pretty much every JDK).

import com.google.web.bindery.autobean.vm.impl.ProxyAutoBean;

public class GwtStackOverFlowException {

	interface MyType<T> {

		T getType();
	}

	public static void main(String[] args) {
		new ProxyAutoBean<>(null, MyType.class, null);
	}
}

@dodgex
Copy link
Contributor

dodgex commented Oct 21, 2023

I have opened PR #9858 which fixed the issue for me.

@bubbleguuum
Copy link
Author

Thanks !
Will likely integrate that fix in my GWT program.

niloc132 pushed a commit that referenced this issue Nov 13, 2023
While the issue (#9855) is related to JDK21 and its new `getFirst` and
`getLast` methods in the `List`/`SequencedCollection` interfaces, the
current behaviour can in theory cause problems on JDK versions before 21
too.

With this fix the `TypeUtils#resolveGenerics` method falls back to
`TypeUtils#ensureBaseType` (already used method-level type variables)
when a type variable is used in the same class where it has been
defined.

This class for example, currently causes a `StackOverflowError`. With
this PR the error is avoided, and the `T` is resolved as
`java.lang.Object`:
```java
class BrokenClass<T> {
    public T getT() {
        return null;
    }
}
```

Fixes #9855
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants