This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 0855c18f1 fix(java): validate subclass serializer layer counts (#3676)
0855c18f1 is described below
commit 0855c18f13534fbf55936e7db043f78a6875af5a
Author: Shawn Yang <[email protected]>
AuthorDate: Wed May 13 13:41:22 2026 +0800
fix(java): validate subclass serializer layer counts (#3676)
## Why?
## What does this PR do?
## Related issues
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes` / `no`
- [ ] If `yes`, I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required `AI Usage Disclosure`.
- [ ] If `yes`, my PR description includes the required `ai_review`
summary and screenshot evidence of the final clean AI review results
from both fresh reviewers on the current PR diff or current HEAD after
the latest code changes.
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
docs/guide/java/static-generated-serializers.md | 20 ++++++++
docs/specification/java_serialization_spec.md | 54 ++++++++++++++++++++++
.../annotation/processing/ForyStructProcessor.java | 24 ++++------
.../processing/ForyStructProcessorTest.java | 40 ++++++++++++++++
.../java/org/apache/fory/annotation/ForyDebug.java | 37 +++++++++++++++
.../org/apache/fory/annotation/ForyStruct.java | 8 ----
.../fory/builder/StaticCompatibleCodecBuilder.java | 6 ++-
.../fory/serializer/ExceptionSerializers.java | 23 +++++++++
.../collection/ChildContainerSerializers.java | 47 +++++++++++++------
.../builder/StaticCompatibleCodecBuilderTest.java | 51 ++++++++++++++++++++
.../fory/serializer/ExceptionSerializersTest.java | 25 ++++++++++
.../collection/ChildContainerSerializersTest.java | 22 +++++++++
12 files changed, 316 insertions(+), 41 deletions(-)
diff --git a/docs/guide/java/static-generated-serializers.md
b/docs/guide/java/static-generated-serializers.md
index ec101027c..428b3eff3 100644
--- a/docs/guide/java/static-generated-serializers.md
+++ b/docs/guide/java/static-generated-serializers.md
@@ -84,6 +84,26 @@ The processor generates serializer classes in the same Java
package as the annot
For a static nested type such as `Outer.Inner`, the generated top-level
classes are
`Outer$Inner__ForySerializer__` and `Outer$Inner__ForyNativeSerializer__`.
+## Field Debug Tracing
+
+Add `@ForyDebug` next to `@ForyStruct` when you need generated serializers to
include field-level
+debug tracing hooks. The generated code prints those traces only when
+`ENABLE_FORY_DEBUG_OUTPUT=1`.
+
+```java
+import org.apache.fory.annotation.ForyDebug;
+import org.apache.fory.annotation.ForyStruct;
+
+@ForyStruct
+@ForyDebug
+public class DebugOrder {
+ public long id;
+ public String note;
+
+ public DebugOrder() {}
+}
+```
+
## Runtime Use
Fory uses static generated serializers when they are available on:
diff --git a/docs/specification/java_serialization_spec.md
b/docs/specification/java_serialization_spec.md
index 7906a329b..1cba88ebc 100644
--- a/docs/specification/java_serialization_spec.md
+++ b/docs/specification/java_serialization_spec.md
@@ -430,6 +430,23 @@ per-element null flags (if HAS_NULL).
If `IS_SAME_TYPE` is not set, each element is written with its own class info
and data (and
optionally ref flags).
+#### Child collection subclasses
+
+Optimized serializers for subclasses of supported JDK collection
implementations write subclass
+field layers before element payloads:
+
+```
+| varuint32_small7: length | [comparator_ref] | varuint32_small7:
num_class_layers |
+| class_layer_fields... | [elements_header | elem_class_info | elements...] |
+```
+
+- `comparator_ref` is present only for sorted-set and priority-queue
subclasses.
+- `num_class_layers` is the exact number of subclass-owned field layers
written after the collection
+ header and before the element payload.
+- Readers must reject a payload whose `num_class_layers` does not match the
local serializer's layer
+ count. These serializers do not carry per-layer class identity in the value
payload, so mismatched
+ layers cannot be skipped safely.
+
### Maps
Maps encode entry count and then a sequence of chunks. Each chunk groups
entries that share key
@@ -471,6 +488,23 @@ These chunks always represent exactly one entry.
- `1`: Java-serialized empty `EnumMap` payload. Android uses this mode when an
empty map has no
public key from which to derive the enum class. Readers on Android and JVM
must accept both modes.
+#### Child map subclasses
+
+Optimized serializers for subclasses of supported JDK map implementations
write subclass field
+layers before map entry chunks:
+
+```
+| varuint32_small7: size | [comparator_ref] | varuint32_small7:
num_class_layers |
+| class_layer_fields... | [chunk_1 | chunk_2 | ...] |
+```
+
+- `comparator_ref` is present only for sorted-map subclasses.
+- `num_class_layers` is the exact number of subclass-owned field layers
written after the map header
+ and before the entry chunks.
+- Readers must reject a payload whose `num_class_layers` does not match the
local serializer's layer
+ count. These serializers do not carry per-layer class identity in the value
payload, so mismatched
+ layers cannot be skipped safely.
+
### JDK collection/map wrappers and views
Java native mode may use specialized serializers for JDK collection/map
wrappers and views. These
@@ -514,6 +548,26 @@ serialization:
- For each field, the serializer uses field metadata (nullable, trackingRef,
polymorphic) to decide
whether to write ref flags and/or type meta before the field value.
+### Throwable values
+
+`Throwable` subclasses use a specialized payload that preserves stack trace,
cause, message,
+suppressed exceptions, and subclass-owned fields:
+
+```
+| stack_trace_ref | cause_ref | message_string_ref |
+| varuint32: suppressed_count | suppressed_ref... |
+| varuint32: extra_field_count | extra_field_name/value... |
+| varuint32_small7: num_class_layers | class_layer_fields... |
+```
+
+- `extra_field_count` is reserved for serializer-owned extension fields and is
currently written as
+ zero.
+- `num_class_layers` is the exact number of `Throwable` subclass field layers
written after the
+ built-in Throwable state.
+- Readers must reject a payload whose `num_class_layers` does not match the
local serializer's layer
+ count. The Throwable value payload does not carry per-layer class identity,
so mismatched layers
+ cannot be skipped safely.
+
### Extensions (EXT)
Extension types are encoded by their registered serializer. Type meta is still
written before the
diff --git
a/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
b/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
index d6827c5d3..7eaa246a4 100644
---
a/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
+++
b/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
@@ -58,12 +58,16 @@ import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
-@SupportedAnnotationTypes("org.apache.fory.annotation.ForyStruct")
+@SupportedAnnotationTypes({
+ "org.apache.fory.annotation.ForyStruct",
+ "org.apache.fory.annotation.ForyDebug"
+})
public final class ForyStructProcessor extends AbstractProcessor {
private static final String ARRAY_TYPE =
"org.apache.fory.annotation.ArrayType";
private static final String BFLOAT16_TYPE =
"org.apache.fory.annotation.BFloat16Type";
private static final String EXPOSE = "org.apache.fory.annotation.Expose";
private static final String FLOAT16_TYPE =
"org.apache.fory.annotation.Float16Type";
+ private static final String FORY_DEBUG =
"org.apache.fory.annotation.ForyDebug";
private static final String FORY_FIELD =
"org.apache.fory.annotation.ForyField";
private static final String FORY_STRUCT =
"org.apache.fory.annotation.ForyStruct";
private static final String IGNORE = "org.apache.fory.annotation.Ignore";
@@ -231,25 +235,13 @@ public final class ForyStructProcessor extends
AbstractProcessor {
canonicalName(type.asType()),
serializerName,
record,
- foryStructDebug(type),
+ isForyDebugEnabled(type),
sourceFields,
recordConstructorFields);
}
- private boolean foryStructDebug(TypeElement type) {
- AnnotationMirror mirror = annotationMirror(type, FORY_STRUCT);
- if (mirror == null) {
- return false;
- }
- Map<? extends ExecutableElement, ? extends AnnotationValue> values =
- elements.getElementValuesWithDefaults(mirror);
- for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue>
entry :
- values.entrySet()) {
- if (entry.getKey().getSimpleName().contentEquals("debug")) {
- return Boolean.TRUE.equals(entry.getValue().getValue());
- }
- }
- return false;
+ private boolean isForyDebugEnabled(TypeElement type) {
+ return annotationMirror(type, FORY_DEBUG) != null;
}
private void validateForyFieldId(
diff --git
a/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
b/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
index a24bc316b..dab9795c2 100644
---
a/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
+++
b/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
@@ -107,6 +107,46 @@ public class ForyStructProcessorTest {
}
}
+ @Test
+ public void testForyDebugAnnotationEmitsGeneratedFieldTracing() throws
Exception {
+ CompilationResult result =
+ compile(
+ "test.DebugStruct",
+ "package test;\n"
+ + "import org.apache.fory.annotation.ForyDebug;\n"
+ + "import org.apache.fory.annotation.ForyStruct;\n"
+ + "@ForyStruct\n"
+ + "@ForyDebug\n"
+ + "public class DebugStruct {\n"
+ + " public int id;\n"
+ + " public String name;\n"
+ + " public DebugStruct() {}\n"
+ + "}\n");
+ Assert.assertTrue(result.success, result.diagnostics());
+ String generatedSource =
+
result.generatedSource("test/DebugStruct__ForyNativeSerializer__.java");
+ Assert.assertTrue(
+ generatedSource.contains("private static final boolean
FORY_STRUCT_DEBUG = true;"));
+ Assert.assertTrue(generatedSource.contains("debugWriteField(\""));
+ Assert.assertTrue(generatedSource.contains("debugReadField(\""));
+ Assert.assertTrue(generatedSource.contains("debugRemoteReadField(\""));
+ }
+
+ @Test
+ public void testForyStructDebugParameterIsRejected() throws Exception {
+ CompilationResult result =
+ compile(
+ "test.LegacyDebugStruct",
+ "package test;\n"
+ + "import org.apache.fory.annotation.ForyStruct;\n"
+ + "@ForyStruct(debug = true) public class LegacyDebugStruct
{\n"
+ + " public int id;\n"
+ + " public LegacyDebugStruct() {}\n"
+ + "}\n");
+ Assert.assertFalse(result.success);
+ Assert.assertTrue(result.diagnostics().contains("debug"),
result.diagnostics());
+ }
+
@Test
public void testPrivateFieldUsesAccessibleAccessors() throws Exception {
CompilationResult result =
diff --git
a/java/fory-core/src/main/java/org/apache/fory/annotation/ForyDebug.java
b/java/fory-core/src/main/java/org/apache/fory/annotation/ForyDebug.java
new file mode 100644
index 000000000..b9aab3c6d
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/annotation/ForyDebug.java
@@ -0,0 +1,37 @@
+/*
+ * 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.fory.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Enables generated serializer field-level debug tracing for a {@link
ForyStruct} type.
+ *
+ * <p>The generated code still prints only when {@code
ENABLE_FORY_DEBUG_OUTPUT=1}; this annotation
+ * controls whether field tracing code is emitted for the annotated struct.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ForyDebug {}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/annotation/ForyStruct.java
b/java/fory-core/src/main/java/org/apache/fory/annotation/ForyStruct.java
index c92d41cc6..a72fb9ff2 100644
--- a/java/fory-core/src/main/java/org/apache/fory/annotation/ForyStruct.java
+++ b/java/fory-core/src/main/java/org/apache/fory/annotation/ForyStruct.java
@@ -58,12 +58,4 @@ public @interface ForyStruct {
* struct encoding even when compatible metadata is otherwise enabled.
*/
Evolution evolution() default Evolution.INHERIT;
-
- /**
- * Emit generated serializer field-level debug tracing.
- *
- * <p>The generated code still prints only when {@code
ENABLE_FORY_DEBUG_OUTPUT=1}; this option
- * controls whether field tracing code is emitted for this struct.
- */
- boolean debug() default false;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/builder/StaticCompatibleCodecBuilder.java
b/java/fory-core/src/main/java/org/apache/fory/builder/StaticCompatibleCodecBuilder.java
index 9bf3f7682..0fbd26f42 100644
---
a/java/fory-core/src/main/java/org/apache/fory/builder/StaticCompatibleCodecBuilder.java
+++
b/java/fory-core/src/main/java/org/apache/fory/builder/StaticCompatibleCodecBuilder.java
@@ -24,6 +24,7 @@ import static org.apache.fory.type.TypeUtils.OBJECT_TYPE;
import java.util.Collections;
import java.util.List;
import org.apache.fory.Fory;
+import org.apache.fory.annotation.ForyDebug;
import org.apache.fory.annotation.ForyStruct;
import org.apache.fory.builder.Generated.GeneratedStaticCompatibleSerializer;
import org.apache.fory.codegen.Code;
@@ -68,8 +69,9 @@ public final class StaticCompatibleCodecBuilder extends
ObjectCodecBuilder {
!fory.getConfig().checkClassVersion(),
"Class version check should be disabled when compatible mode is
enabled.");
localDescriptors =
Collections.unmodifiableList(Descriptor.getDescriptors(beanClass));
- ForyStruct foryStruct = beanClass.getAnnotation(ForyStruct.class);
- debug = foryStruct != null && foryStruct.debug();
+ debug =
+ beanClass.isAnnotationPresent(ForyStruct.class)
+ && beanClass.isAnnotationPresent(ForyDebug.class);
}
@Override
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
index 5efb771ff..dc22b47ed 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
@@ -100,6 +100,7 @@ public final class ExceptionSerializers {
writeContext.writeRef(suppressedException);
}
buffer.writeVarUInt32(0);
+ writeNumClassLayers(buffer, slotsSerializers);
for (Serializer slotsSerializer : slotsSerializers) {
slotsSerializer.write(writeContext, value);
}
@@ -214,6 +215,10 @@ public final class ExceptionSerializers {
readContext.readRef();
}
}
+
+ private void writeNumClassLayers(MemoryBuffer buffer, Serializer[]
slotsSerializers) {
+ buffer.writeVarUInt32Small7(slotsSerializers.length);
+ }
}
public static final class StackTraceElementSerializer extends
Serializer<StackTraceElement> {
@@ -425,6 +430,7 @@ public final class ExceptionSerializers {
private static void readAndSetFields(
ReadContext readContext, Object target, Serializer[] slotsSerializers,
Config config) {
+ readAndCheckNumClassLayers(readContext, target.getClass(),
slotsSerializers.length);
for (Serializer slotsSerializer : slotsSerializers) {
if (slotsSerializer instanceof CompatibleLayerSerializer) {
CompatibleLayerSerializer compatibleSerializer =
@@ -439,6 +445,23 @@ public final class ExceptionSerializers {
}
}
+ private static void readAndCheckNumClassLayers(
+ ReadContext readContext, Class<?> type, int expectedNumClassLayers) {
+ MemoryBuffer buffer = readContext.getBuffer();
+ int numClassLayers = buffer.readVarUInt32Small7();
+ if (numClassLayers != expectedNumClassLayers) {
+ // Layer payloads do not carry per-layer class identity here, so
mismatches cannot be skipped
+ // safely. Fail before consuming field payloads.
+ throw new ForyException(
+ "Class layer count mismatch for Throwable type "
+ + type.getName()
+ + ": expected "
+ + expectedNumClassLayers
+ + ", got "
+ + numClassLayers);
+ }
+ }
+
private static void readAndSkipLayerClassMeta(ReadContext readContext) {
MemoryBuffer buffer = readContext.getBuffer();
MetaReadContext metaReadContext = readContext.getMetaReadContext();
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
index b78172dc1..4a80478a3 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
@@ -176,9 +176,7 @@ public class ChildContainerSerializers {
public Collection onCollectionWrite(WriteContext writeContext, T value) {
MemoryBuffer buffer = writeContext.getBuffer();
buffer.writeVarUInt32Small7(value.size());
- for (Serializer slotsSerializer : slotsSerializers) {
- slotsSerializer.write(writeContext, value);
- }
+ writeChildFields(writeContext, value, slotsSerializers);
return value;
}
@@ -245,9 +243,7 @@ public class ChildContainerSerializers {
MemoryBuffer buffer = writeContext.getBuffer();
buffer.writeVarUInt32Small7(value.size());
writeContext.writeRef(value.comparator());
- for (Serializer slotsSerializer : slotsSerializers) {
- slotsSerializer.write(writeContext, value);
- }
+ writeChildFields(writeContext, value, slotsSerializers);
return value;
}
@@ -293,9 +289,7 @@ public class ChildContainerSerializers {
MemoryBuffer buffer = writeContext.getBuffer();
buffer.writeVarUInt32Small7(value.size());
writeContext.writeRef(value.comparator());
- for (Serializer slotsSerializer : slotsSerializers) {
- slotsSerializer.write(writeContext, value);
- }
+ writeChildFields(writeContext, value, slotsSerializers);
return value;
}
@@ -352,9 +346,7 @@ public class ChildContainerSerializers {
public Map onMapWrite(WriteContext writeContext, T value) {
MemoryBuffer buffer = writeContext.getBuffer();
buffer.writeVarUInt32Small7(value.size());
- for (Serializer slotsSerializer : slotsSerializers) {
- slotsSerializer.write(writeContext, value);
- }
+ writeChildFields(writeContext, value, slotsSerializers);
return value;
}
@@ -405,9 +397,7 @@ public class ChildContainerSerializers {
MemoryBuffer buffer = writeContext.getBuffer();
buffer.writeVarUInt32Small7(value.size());
writeContext.writeRef(value.comparator());
- for (Serializer slotsSerializer : slotsSerializers) {
- slotsSerializer.write(writeContext, value);
- }
+ writeChildFields(writeContext, value, slotsSerializers);
return value;
}
@@ -651,6 +641,7 @@ public class ChildContainerSerializers {
TypeResolver typeResolver,
Object collection,
Serializer[] slotsSerializers) {
+ readAndCheckNumClassLayers(readContext, collection.getClass(),
slotsSerializers.length);
for (Serializer slotsSerializer : slotsSerializers) {
if (slotsSerializer instanceof CompatibleLayerSerializer) {
CompatibleLayerSerializer compatibleSerializer =
@@ -667,6 +658,32 @@ public class ChildContainerSerializers {
}
}
+ private static void writeChildFields(
+ WriteContext writeContext, Object collection, Serializer[]
slotsSerializers) {
+ MemoryBuffer buffer = writeContext.getBuffer();
+ buffer.writeVarUInt32Small7(slotsSerializers.length);
+ for (Serializer slotsSerializer : slotsSerializers) {
+ slotsSerializer.write(writeContext, collection);
+ }
+ }
+
+ private static void readAndCheckNumClassLayers(
+ ReadContext readContext, Class<?> type, int expectedNumClassLayers) {
+ MemoryBuffer buffer = readContext.getBuffer();
+ int numClassLayers = buffer.readVarUInt32Small7();
+ if (numClassLayers != expectedNumClassLayers) {
+ // Layer payloads do not carry per-layer class identity here, so
mismatches cannot be skipped
+ // safely. Fail before consuming field payloads.
+ throw new ForyException(
+ "Class layer count mismatch for child container type "
+ + type.getName()
+ + ": expected "
+ + expectedNumClassLayers
+ + ", got "
+ + numClassLayers);
+ }
+ }
+
/**
* Read and skip the layer class meta from buffer. This is used to skip over
the class definition
* that was written by CompatibleLayerSerializer.writeLayerClassMeta(). For
diff --git
a/java/fory-core/src/test/java/org/apache/fory/builder/StaticCompatibleCodecBuilderTest.java
b/java/fory-core/src/test/java/org/apache/fory/builder/StaticCompatibleCodecBuilderTest.java
index 23a9731b3..8289a6f5a 100644
---
a/java/fory-core/src/test/java/org/apache/fory/builder/StaticCompatibleCodecBuilderTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/builder/StaticCompatibleCodecBuilderTest.java
@@ -47,6 +47,7 @@ import org.apache.fory.context.MetaReadContext;
import org.apache.fory.context.MetaWriteContext;
import org.apache.fory.meta.TypeDef;
import org.apache.fory.platform.GraalvmSupport;
+import org.apache.fory.reflect.TypeRef;
import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.serializer.Serializer;
import org.mockito.MockedStatic;
@@ -62,6 +63,47 @@ public class StaticCompatibleCodecBuilderTest {
return new Object[][] {{false}, {true}};
}
+ @Test
+ public void testForyDebugAnnotationEnablesStaticCompatibleTracing() throws
Exception {
+ CompilationResult debugResult =
+ compile(
+ "test.StaticCompatibleDebugPayload",
+ "package test;\n"
+ + "import org.apache.fory.annotation.ForyDebug;\n"
+ + "import org.apache.fory.annotation.ForyStruct;\n"
+ + "@ForyStruct\n"
+ + "@ForyDebug\n"
+ + "public class StaticCompatibleDebugPayload {\n"
+ + " public int id;\n"
+ + " public StaticCompatibleDebugPayload() {}\n"
+ + "}\n");
+ CompilationResult plainResult =
+ compile(
+ "test.StaticCompatiblePlainPayload",
+ "package test;\n"
+ + "import org.apache.fory.annotation.ForyStruct;\n"
+ + "@ForyStruct\n"
+ + "public class StaticCompatiblePlainPayload {\n"
+ + " public int id;\n"
+ + " public StaticCompatiblePlainPayload() {}\n"
+ + "}\n");
+ Assert.assertTrue(debugResult.success, debugResult.diagnostics());
+ Assert.assertTrue(plainResult.success, plainResult.diagnostics());
+ try (URLClassLoader debugLoader = debugResult.classLoader();
+ URLClassLoader plainLoader = plainResult.classLoader()) {
+ assertStaticCompatibleDebugTracing(
+ debugLoader,
+ debugLoader.loadClass("test.StaticCompatibleDebugPayload"),
+ true,
+ "debug-reader");
+ assertStaticCompatibleDebugTracing(
+ plainLoader,
+ plainLoader.loadClass("test.StaticCompatiblePlainPayload"),
+ false,
+ "plain-reader");
+ }
+ }
+
@Test(dataProvider = "xlangModes")
public void testStaticCompatibleSerializerReadsRuntimeRemoteTypeDef(boolean
xlang)
throws Exception {
@@ -394,6 +436,15 @@ public class StaticCompatibleCodecBuilderTest {
return fory;
}
+ private static void assertStaticCompatibleDebugTracing(
+ ClassLoader classLoader, Class<?> type, boolean expectedDebug, String
role) {
+ Fory fory = compatibleFory(classLoader, type, false, role);
+ TypeDef typeDef = TypeDef.buildTypeDef(fory.getTypeResolver(), type);
+ String generatedSource =
+ new StaticCompatibleCodecBuilder(TypeRef.of(type), fory,
typeDef).genCode();
+ Assert.assertEquals(generatedSource.contains("debugRemoteReadField(\""),
expectedDebug);
+ }
+
@SuppressWarnings("unchecked")
private static Class<Object> cast(Class<?> type) {
return (Class<Object>) type;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
index ff1b4cd39..2b0505e07 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
@@ -26,6 +26,7 @@ import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
+import org.apache.fory.exception.ForyException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.MemoryUtils;
import org.apache.fory.reflect.ReflectionUtils;
@@ -174,6 +175,7 @@ public class ExceptionSerializersTest extends ForyTestBase {
writeContext.writeStringRef(value.getMessage());
payload.writeVarUInt32(0);
payload.writeVarUInt32(0);
+ payload.writeVarUInt32Small7(2);
payload.readerIndex(0);
ReadContext readContext = fory.getReadContext();
@@ -233,6 +235,29 @@ public class ExceptionSerializersTest extends ForyTestBase
{
Assert.assertEquals(copy.getSuppressed()[0].getMessage(),
"suppressed-custom");
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testThrowableRejectsMismatchedClassLayerCount() {
+ Fory fory =
builder().withRefTracking(false).withCodegen(false).withCompatible(true).build();
+ MemoryBuffer payload = MemoryUtils.buffer(128);
+ WriteContext writeContext = fory.getWriteContext();
+ writeContext.prepare(payload, null);
+ writeContext.writeRef(null);
+ writeContext.writeRef(null);
+ writeContext.writeStringRef("layer-count");
+ payload.writeVarUInt32(0);
+ payload.writeVarUInt32(0);
+ payload.writeVarUInt32Small7(0);
+
+ payload.readerIndex(0);
+ ReadContext readContext = fory.getReadContext();
+ readContext.prepare(payload, null, false);
+ Serializer<CustomException> serializer =
+ (Serializer<CustomException>)
fory.getTypeResolver().getSerializer(CustomException.class);
+
+ Assert.assertThrows(ForyException.class, () ->
serializer.read(readContext));
+ }
+
private static RuntimeException buildTryWithResourcesException() {
try {
try (FailingCloseable ignored = new FailingCloseable()) {
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
index 96d22fc8a..8ec956085 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
@@ -48,6 +48,11 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
+import org.apache.fory.context.ReadContext;
+import org.apache.fory.exception.ForyException;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.memory.MemoryUtils;
+import org.apache.fory.serializer.Serializer;
import org.apache.fory.test.bean.Cyclic;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
@@ -128,6 +133,23 @@ public class ChildContainerSerializersTest extends
ForyTestBase {
}
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testChildCollectionRejectsMismatchedClassLayerCount() {
+ Fory fory =
builder().withRefTracking(false).withCodegen(false).withCompatible(true).build();
+ MemoryBuffer payload = MemoryUtils.buffer(32);
+ payload.writeVarUInt32Small7(0);
+ payload.writeVarUInt32Small7(0);
+
+ payload.readerIndex(0);
+ ReadContext readContext = fory.getReadContext();
+ readContext.prepare(payload, null, false);
+ Serializer<ChildArrayList> serializer =
+ (Serializer<ChildArrayList>)
fory.getTypeResolver().getSerializer(ChildArrayList.class);
+
+ Assert.assertThrows(ForyException.class, () ->
serializer.read(readContext));
+ }
+
@Test(dataProvider = "foryCopyConfig")
public void testChildCollectionCopy(Fory fory) {
List<Object> data = ImmutableList.of(1, true, "test", Cyclic.create(true));
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]