From ab8f48040df7d0faef6710af8e26598cd053ccfe Mon Sep 17 00:00:00 2001 From: Shawn Yang Date: Sat, 23 Mar 2024 14:54:26 +0800 Subject: [PATCH] feat(java): carry read objects when deserialization fail for better trouble shooting (#1420) This PR carry read objects when deserialization fail for better trouble shooting. Closes #1419 --- .../src/main/java/org/apache/fury/Fury.java | 29 +++++++++- .../apache/fury/collection/ObjectArray.java | 8 +++ .../exception/DeserializationException.java | 54 +++++++++++++++++++ .../apache/fury/resolver/MapRefResolver.java | 4 ++ .../test/java/org/apache/fury/FuryTest.java | 27 ++++++++++ 5 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 java/fury-core/src/main/java/org/apache/fury/exception/DeserializationException.java diff --git a/java/fury-core/src/main/java/org/apache/fury/Fury.java b/java/fury-core/src/main/java/org/apache/fury/Fury.java index 3dc1c16b3d..407ae72593 100644 --- a/java/fury-core/src/main/java/org/apache/fury/Fury.java +++ b/java/fury-core/src/main/java/org/apache/fury/Fury.java @@ -26,17 +26,20 @@ import java.io.OutputStream; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.concurrent.NotThreadSafe; import org.apache.fury.builder.JITContext; +import org.apache.fury.collection.ObjectArray; import org.apache.fury.config.CompatibleMode; import org.apache.fury.config.Config; import org.apache.fury.config.FuryBuilder; import org.apache.fury.config.Language; import org.apache.fury.config.LongEncoding; +import org.apache.fury.exception.DeserializationException; import org.apache.fury.memory.MemoryBuffer; import org.apache.fury.memory.MemoryUtils; import org.apache.fury.resolver.ClassInfo; @@ -59,6 +62,7 @@ import org.apache.fury.type.Type; import org.apache.fury.util.ExceptionUtils; import org.apache.fury.util.LoggerFactory; +import org.apache.fury.util.Platform; import org.apache.fury.util.Preconditions; import org.apache.fury.util.StringUtils; import org.slf4j.Logger; @@ -747,6 +751,9 @@ public Object deserialize(MemoryBuffer buffer, Iterable outOfBandB obj = readRef(buffer); } return obj; + } catch (Throwable t) { + handleReadFailed(t); + throw new IllegalStateException("unreachable"); } finally { resetRead(); jitContext.unlock(); @@ -769,6 +776,17 @@ public Object deserialize(InputStream inputStream, Iterable outOfB } } + private void handleReadFailed(Throwable t) { + if (refResolver instanceof MapRefResolver) { + ObjectArray readObjects = ((MapRefResolver) refResolver).getReadObjects(); + // carry with read objects for better trouble shooting. + List objects = Arrays.asList(readObjects.objects).subList(0, readObjects.size); + throw new DeserializationException(objects, t); + } else { + Platform.throwException(t); + } + } + private Object xdeserializeInternal(MemoryBuffer buffer) { Object obj; int nativeObjectsStartOffset = buffer.readInt(); @@ -1051,6 +1069,9 @@ public T deserializeJavaObject(MemoryBuffer buffer, Class cls) { } else { return null; } + } catch (Throwable t) { + handleReadFailed(t); + throw new IllegalStateException("unreachable"); } finally { resetRead(); jitContext.unlock(); @@ -1124,6 +1145,9 @@ public Object deserializeJavaObjectAndClass(MemoryBuffer buffer) { classResolver.readClassDefs(buffer); } return readRef(buffer); + } catch (Throwable t) { + handleReadFailed(t); + throw new IllegalStateException("unreachable"); } finally { resetRead(); jitContext.unlock(); @@ -1190,8 +1214,9 @@ private Object deserializeFromStream( buf.pointTo(oldBytes, 0, oldBytes.length); } return o; - } catch (IOException e) { - throw new RuntimeException(e); + } catch (Throwable t) { + handleReadFailed(t); + throw new IllegalStateException("unreachable"); } finally { resetBuffer(); } diff --git a/java/fury-core/src/main/java/org/apache/fury/collection/ObjectArray.java b/java/fury-core/src/main/java/org/apache/fury/collection/ObjectArray.java index 33a3d8cac2..578f9c5662 100644 --- a/java/fury-core/src/main/java/org/apache/fury/collection/ObjectArray.java +++ b/java/fury-core/src/main/java/org/apache/fury/collection/ObjectArray.java @@ -109,4 +109,12 @@ public static void clearObjectArray(Object[] objects, int start, int size) { } } } + + @Override + public String toString() { + if (size == 0) { + return "[]"; + } + return Arrays.asList(objects).subList(0, size).toString(); + } } diff --git a/java/fury-core/src/main/java/org/apache/fury/exception/DeserializationException.java b/java/fury-core/src/main/java/org/apache/fury/exception/DeserializationException.java new file mode 100644 index 0000000000..11d9691ad2 --- /dev/null +++ b/java/fury-core/src/main/java/org/apache/fury/exception/DeserializationException.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fury.exception; + +import java.util.List; + +public class DeserializationException extends FuryException { + + private List readObjects; + + public DeserializationException(String message) { + super(message); + } + + public DeserializationException(Throwable cause) { + super(cause); + } + + public DeserializationException(String message, Throwable cause) { + super(message, cause); + } + + // if `readObjects` too big, generate message lazily to avoid big string creation cost. + public DeserializationException(List readObjects, Throwable cause) { + super(cause); + this.readObjects = readObjects; + } + + @Override + public String getMessage() { + if (readObjects == null) { + return super.getMessage(); + } else { + return "Deserialize failed, read objects are: " + readObjects; + } + } +} diff --git a/java/fury-core/src/main/java/org/apache/fury/resolver/MapRefResolver.java b/java/fury-core/src/main/java/org/apache/fury/resolver/MapRefResolver.java index fbfcfebcd2..b04d24a162 100644 --- a/java/fury-core/src/main/java/org/apache/fury/resolver/MapRefResolver.java +++ b/java/fury-core/src/main/java/org/apache/fury/resolver/MapRefResolver.java @@ -211,6 +211,10 @@ public void setReadObject(int id, Object object) { } } + public ObjectArray getReadObjects() { + return readObjects; + } + @Override public void reset() { resetWrite(); diff --git a/java/fury-core/src/test/java/org/apache/fury/FuryTest.java b/java/fury-core/src/test/java/org/apache/fury/FuryTest.java index a4c34ba749..8baf980d81 100644 --- a/java/fury-core/src/test/java/org/apache/fury/FuryTest.java +++ b/java/fury-core/src/test/java/org/apache/fury/FuryTest.java @@ -56,6 +56,7 @@ import org.apache.fury.builder.Generated; import org.apache.fury.config.FuryBuilder; import org.apache.fury.config.Language; +import org.apache.fury.exception.FuryException; import org.apache.fury.exception.InsecureException; import org.apache.fury.memory.MemoryBuffer; import org.apache.fury.memory.MemoryUtils; @@ -663,4 +664,30 @@ private void checkBuffer(Fury fury) { assert buffer != null; assertTrue(buffer.size() < 1000 * 1000); } + + @Data + static class PrintReadObject { + public PrintReadObject() { + throw new RuntimeException(); + } + + public PrintReadObject(boolean b) {} + } + + @Test + public void testPrintReadObjectsWhenFailed() { + Fury fury = + Fury.builder() + .withRefTracking(true) + .withCodegen(false) + .requireClassRegistration(false) + .build(); + PrintReadObject o = new PrintReadObject(true); + try { + serDe(fury, ImmutableList.of(ImmutableList.of("a", "b"), o)); + Assert.fail(); + } catch (FuryException e) { + Assert.assertTrue(e.getMessage().contains("[a, b]")); + } + } }