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

The types returned by newly added List/Set/Map.of/copyOf() cannot be passed between client and server (=are outside the scope of gwt rpc serialization) #10048

Open
andreaskornstaedt opened this issue Nov 23, 2024 · 5 comments

Comments

@andreaskornstaedt
Copy link

**GWT version: 2.12.1
**Browser (with version): any
**Operating System: any


Description

For example, having a GWT service method that returns a List instance created using List.of() will yield the following at runtime:

2024-11-23 18:36:41.736:WARN:oejshC.ROOT:qtp29640057-62: Exception while dispatching incoming RPC call
com.google.gwt.user.client.rpc.SerializationException: Type 'java.util.ImmutableCollections$ListN' was not included in the set of types which can be serialized by this SerializationPolicy or its Class object could not be loaded. For security purposes, this type will not be serialized.: instance = [1, 2, 3]
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:699)
	at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:130)
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:169)
	at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:609)
	at com.google.gwt.user.server.rpc.RPC.encodeResponse(RPC.java:644)
	at com.google.gwt.user.server.rpc.RPC.encodeResponseForSuccess(RPC.java:497)
	at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:589)
	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:349)
	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:319)
	at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:389)
	at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
	at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service(ServletHolder.java:1450)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799)
	at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1631)
	at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:228)
	at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193)
	at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:548)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:600)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1434)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1349)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
	at org.eclipse.jetty.server.handler.RequestLogHandler.handle(RequestLogHandler.java:54)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.Server.handle(Server.java:516)
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:400)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:645)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:392)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
	at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
	at java.base/java.lang.Thread.run(Thread.java:1583)

Steps to reproduce

Create a GWT service like this

public interface MyService extends RemoteService {
	List<String> getList();
} 

with the matching AsyncService (skipped here because it's obvious) and an implementation like this

public class MyServiceImpl extends RemoteServiceServlet implements MyService {
	@Override
	public List<String> getList() {
		return List.of("1", "2", "3");
	}
}

and call it in your client code like this:

	@Override
	public void onModuleLoad() {
		visualsService.getList(new AsyncCallback<List<String>>() {

			@Override
			public void onSuccess(List<String> result) {
				GWT.log("success: " + result);
			}

			@Override
			public void onFailure(Throwable caught) {
				GWT.log("fail");
			}
		});
	}

The same happens for Set.of(), Map.of() and the pertinent copyOf() methods.

Eventually, all these methods create instances of java.util.ImmutableCollections$ListN, List12, etc. which do not partake in gwt rpc serialization as stated in the exception message.

Since all those classes explicitly implement java.io.Serializable this will startle users who think that they can use List.of() / List.copyOf() since 2.11/2.12

Known workarounds

Continue using Arrays.asList(), etc.

Links to further discussions
@1jkoch
Copy link

1jkoch commented Nov 24, 2024

Although workarounds exist, issues like this force reliance on older language features to avoid runtime type errors, driving GWT adoption in my environment to near zero :,(

@zbynek
Copy link
Contributor

zbynek commented Nov 24, 2024

Is it the same underlying issue as #7247 ?

@andreaskornstaedt
Copy link
Author

Is it the same underlying issue as #7247 ?

Even though this issue also involves gwt.rpc, this issue occurs ALWAYS and not just within the limited scope described in #7247:

The problem occurs in SerializableTypeOracleBuilder.checkAllSubtypesOfObject. It requires
a combination of something tirggering checkAllSubtypesOfObject (for instance a use
of a raw type) and an interface that only has a single class (in theory, it could occur
with multiple subclasses too).

@niloc132
Copy link
Contributor

Thanks for the report - there are probably some tricks that could make this possible, but to my knowledge there's nothing identifiable that you can do in user code to cleanly spot that you've received an immutable list like this - ListN and friends are not public, nor is there some ImmutableList interface nor some isImmutable/isMutable on collections/etc. Arrays.asList is implemented by trying to mirror the internal structure of java.util.Arrays (modifications to the array must be reflected in the List), but in GWT the relative cost of creating new classes is much higher than in Java so when there is no extra features afforded by List.of(item) that Collections.singletonList(item) doesn't grant. Except: the class instances won't match.

Take a look at #3071 for example - this is not a recent departure from how the GWT project has treated this topic in the past, nor is it just an oversight in this release. Keep in mind that when a feature like this is implemented, the costs may be shared among all projects that happen to use List.of(), whether or not they also use GWT-RPC - or vice versa. 45c0555 is the revert commit that removed that feature.

@1jkoch I'm sorry this is inconveniencing you and your adoption of new features. Keep in mind that the GWT Project is almost entirely volunteer resourced - that includes triage of issues, development of features and bugfixes, and acceptance testing to decide that a release is "finished". While my company does accept money in exchange for improvements in the compiler and ecosystem at large, we find that (much) less than 5% of our revenue is actually directed at doing such work (but this fact doesn't stop us from continuing to work on GWT with the time we have available). Also note that this does not prevent you from using List.of() and friends, but only from serializing them via GWT-RPC, and these are far from the only types that are serializable by ObjectOutputStream that are not supported by GWT-RPC.

@zbynek no - the problem here is that the server has different List implementations (going by FQCN and fields) than the client emulation does, and so needs some custom handling to address this. That issue is an edge case in how to proceed in cases with raw generics. Serializability via Java generics is already undecideable, and raw generics are a great way to let the serializable type list explode, so I have to discourage that from the get go - but going by the history of that patch, it just needs a steward to complete it with tests so we can land it.

@1jkoch
Copy link

1jkoch commented Nov 24, 2024 via email

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

No branches or pull requests

4 participants