From f87fdb1557db243c08927bb2d462793c40a9c195 Mon Sep 17 00:00:00 2001 From: Marco Collovati Date: Tue, 23 Apr 2024 10:13:21 +0200 Subject: [PATCH] fix: unwrap lambda deserialization cause (#136) Base on the JDK version, SerializedLambda.readResolve may differently wrap the cause of error. This change analyzes the exception to unwrap the root cause of the failure, allowing the debug tool to provide the trace of potential unserializable lambda candidates. References #135 --- .../serialization/debug/Job.java | 22 ++++++++-- .../SerializationDebugRequestHandlerTest.java | 42 +++++++++++++++++-- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/kubernetes-kit-starter/src/main/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/Job.java b/kubernetes-kit-starter/src/main/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/Job.java index 4d0d90d..aa74207 100644 --- a/kubernetes-kit-starter/src/main/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/Job.java +++ b/kubernetes-kit-starter/src/main/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/Job.java @@ -9,8 +9,10 @@ */ package com.vaadin.kubernetes.starter.sessiontracker.serialization.debug; +import java.io.InvalidObjectException; import java.io.ObjectStreamClass; import java.lang.invoke.SerializedLambda; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; @@ -194,10 +196,11 @@ void deserializationFailed(Exception ex) { outcome.add(Outcome.DESERIALIZATION_FAILED); log(CATEGORY_ERRORS, Outcome.DESERIALIZATION_FAILED.name() + ": " + ex.getMessage()); - if (ex instanceof ClassCastException - && ex.getMessage().contains(SerializedLambda.class.getName()) + Throwable cause = tryUnwrapLambdaDeserializationCause(ex); + if (cause instanceof ClassCastException + && cause.getMessage().contains(SerializedLambda.class.getName()) && !serializedLambdaMap.isEmpty()) { - String targetType = tryDetectClassCastTarget(ex.getMessage()); + String targetType = tryDetectClassCastTarget(cause.getMessage()); if (targetType != null) { String bestCandidates = serializedLambdaMap.values().stream() .filter(serializedLambda -> serializedLambda @@ -223,6 +226,19 @@ void deserializationFailed(Exception ex) { } } + // Base on the JDK version, SerializedLambda.readResolve may differently + // wrap the cause of error + private Throwable tryUnwrapLambdaDeserializationCause(Throwable exception) { + Throwable cause = exception; + if (cause instanceof InvalidObjectException) { + cause = cause.getCause(); + } + if (cause instanceof InvocationTargetException) { + cause = cause.getCause(); + } + return cause; + } + private Logger getLogger() { return LoggerFactory.getLogger(DebugMode.class); } diff --git a/kubernetes-kit-starter/src/test/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/SerializationDebugRequestHandlerTest.java b/kubernetes-kit-starter/src/test/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/SerializationDebugRequestHandlerTest.java index d7eaaa3..9cdb1ed 100644 --- a/kubernetes-kit-starter/src/test/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/SerializationDebugRequestHandlerTest.java +++ b/kubernetes-kit-starter/src/test/java/com/vaadin/kubernetes/starter/sessiontracker/serialization/debug/SerializationDebugRequestHandlerTest.java @@ -1,21 +1,23 @@ package com.vaadin.kubernetes.starter.sessiontracker.serialization.debug; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.invoke.SerializedLambda; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpSession; import org.assertj.core.api.SoftAssertions; import org.assertj.core.data.Index; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.springframework.mock.web.MockHttpSession; @@ -314,8 +316,40 @@ void handleRequest_serializationTimeout_timeoutReported() { } @Test - @Disabled("Find a way to simulate SerializedLambda ClassCastException") void handleRequest_lambdaSelfReferenceClassCast_errorCaught() { + + LambdaDeserializationClassCast holder = new LambdaDeserializationClassCast(); + httpSession.setAttribute("OBJ1", holder.selfReferenceLambda); + + runDebugTool(); + Result result = resultHolder.get(); + assertThat(result.getOutcomes()) + .containsExactly(Outcome.DESERIALIZATION_FAILED); + String unserializableInfo = String.join(System.lineSeparator(), + result.getErrors()); + assertThat(unserializableInfo) + .contains("cannot assign instance of " + + SerializedLambda.class.getName()) + .contains("field " + + LambdaDeserializationClassCast.class.getName() + + ".selfReferenceLambda") + .contains("in instance of " + + LambdaDeserializationClassCast.class.getName()) + .contains( + "SERIALIZED LAMBDA CLASS CAST EXCEPTION BEST CANDIDATES") + .contains("SerializedLambda[capturingClass=class " + + LambdaDeserializationClassCast.class.getName()); + } + + static class LambdaDeserializationClassCast implements Serializable { + + private final Serializable capture = new Serializable() { + }; + + private final SerializableRunnable selfReferenceLambda = () -> { + Objects.requireNonNull(capture); + }; + } private void assertDebugToolNotExecuted() {