Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,39 @@ public final class ASMUtils {
descMapping.put(FieldReader[].class, DESC_FIELD_READER_ARRAY);
}

/**
* Sanitizes a class simple name so that it can be embedded in a generated class name.
* Array types like {@code "Class[]"} become {@code "ClassArray"}, and any other character
* that is not valid in a class name is replaced with an underscore.
*
* @param simpleName the simple name to sanitize, may be null
* @return a name safe to use in a generated class name, never null
*/
public static String sanitizeClassName(String simpleName) {
if (simpleName == null || simpleName.isEmpty()) {
return "";
}
if (simpleName.endsWith("[]")) {
return sanitizeClassName(simpleName.substring(0, simpleName.length() - 2)) + "Array";
}
StringBuilder buf = null;
for (int i = 0; i < simpleName.length(); i++) {
char c = simpleName.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') {
if (buf != null) {
buf.append(c);
}
} else {
if (buf == null) {
buf = new StringBuilder(simpleName.length());
buf.append(simpleName, 0, i);
}
buf.append('_');
}
}
return buf != null ? buf.toString() : simpleName;
}

public static String type(Class<?> clazz) {
String type = typeMapping.get(clazz);
if (type != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4173,7 +4173,7 @@ public ObjectReadContext(
this.fieldNameCharLengthMin = charLenMin;
this.fieldNameCharLengthMax = charLenMax;

String className = "ORG_" + seed.incrementAndGet() + "_" + fieldReaders.length + (objectClass == null ? "" : "_" + objectClass.getSimpleName());
String className = "ORG_" + seed.incrementAndGet() + "_" + fieldReaders.length + (objectClass == null ? "" : "_" + sanitizeClassName(objectClass.getSimpleName()));

Package pkg = ObjectReaderCreatorASM.class.getPackage();
if (pkg != null) {
Expand Down Expand Up @@ -4237,7 +4237,7 @@ private Function createValueConsumer0(
String className = (bytes ? "VBACG_" : "VCACG_")
+ seed.incrementAndGet()
+ "_" + fieldReaderArray.length
+ "_" + objectClass.getSimpleName();
+ "_" + sanitizeClassName(objectClass.getSimpleName());
String classNameType;
String classNameFull;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ private ObjectWriterAdapter jitWriter(

ClassWriter cw = new ClassWriter(null);

String className = "OWG_" + seed.incrementAndGet() + "_" + fieldWriters.size() + (objectClass == null ? "" : ("_" + objectClass.getSimpleName()));
String className = "OWG_" + seed.incrementAndGet() + "_" + fieldWriters.size() + (objectClass == null ? "" : ("_" + sanitizeClassName(objectClass.getSimpleName())));
String classNameType;
String classNameFull;

Expand Down Expand Up @@ -4636,7 +4636,7 @@ private FieldWriter jitFieldWriterList(

ClassWriter cw = new ClassWriter(null);

String className = "OWF_" + seed.incrementAndGet() + "_" + fieldWriters.size() + "_" + itemClass.getSimpleName();
String className = "OWF_" + seed.incrementAndGet() + "_" + fieldWriters.size() + "_" + sanitizeClassName(itemClass.getSimpleName());
String classNameType;
String classNameFull;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.alibaba.fastjson2.issues_4000;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class Issue4009Test {
@Test
public void testClassArrayField() {
// Test that a class with a Class[] field can be serialized without ClassFormatError
BeanWithClassArray bean = new BeanWithClassArray();
bean.setClasses(new Class[]{String.class, Integer.class});
bean.setName("test");

// This should not throw ClassFormatError
Object json = JSON.toJSON(bean);
assertNotNull(json);
Comment thread
wenshao marked this conversation as resolved.

String jsonString = JSON.toJSONString(bean);
assertNotNull(jsonString);
assertTrue(jsonString.contains("test"));

// the reader generates class names the same way, so deserialization must work too
// (SupportClassForName is required only because the field type is Class[])
BeanWithClassArray parsed = JSON.parseObject(
jsonString, BeanWithClassArray.class, JSONReader.Feature.SupportClassForName);
assertNotNull(parsed);
assertEquals("test", parsed.getName());
assertArrayEquals(new Class[]{String.class, Integer.class}, parsed.getClasses());
}

@Test
public void testClassArrayFieldNull() {
BeanWithClassArray bean = new BeanWithClassArray();
bean.setClasses(null);
bean.setName("test");

Object json = JSON.toJSON(bean);
assertNotNull(json);
}

@Test
public void test2DArrayField() {
// Test multi-dimensional arrays
BeanWith2DArray bean = new BeanWith2DArray();
bean.setMatrix(new int[][]{{1, 2}, {3, 4}});

Object json = JSON.toJSON(bean);
assertNotNull(json);

String jsonString = JSON.toJSONString(bean);
assertNotNull(jsonString);

BeanWith2DArray parsed = JSON.parseObject(jsonString, BeanWith2DArray.class);
assertNotNull(parsed);
assertArrayEquals(new int[][]{{1, 2}, {3, 4}}, parsed.getMatrix());
}

public static class BeanWithClassArray {
private String name;
private Class<?>[] classes;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Class<?>[] getClasses() {
return classes;
}

public void setClasses(Class<?>[] classes) {
this.classes = classes;
}
}

public static class BeanWith2DArray {
private int[][] matrix;

public int[][] getMatrix() {
return matrix;
}

public void setMatrix(int[][] matrix) {
this.matrix = matrix;
}
}
}
30 changes: 30 additions & 0 deletions core/src/test/java/com/alibaba/fastjson2/util/ASMUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import java.time.format.DateTimeParseException;

import static com.alibaba.fastjson2.internal.asm.ASMUtils.lookupParameterNames;
import static com.alibaba.fastjson2.internal.asm.ASMUtils.sanitizeClassName;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;

@Tag("util")
public class ASMUtilsTest {
Expand Down Expand Up @@ -48,4 +50,32 @@ public void test1() throws Exception {
)
);
}

@Test
public void sanitizeClassNameNullOrEmpty() {
assertEquals("", sanitizeClassName(null));
assertEquals("", sanitizeClassName(""));
}

@Test
public void sanitizeClassNameUnchanged() {
assertEquals("String", sanitizeClassName("String"));
assertEquals("MyClass", sanitizeClassName("MyClass"));
assertEquals("MyClass2", sanitizeClassName("MyClass2"));
assertEquals("My_Class", sanitizeClassName("My_Class"));
}

@Test
public void sanitizeClassNameArray() {
assertEquals("ClassArray", sanitizeClassName("Class[]"));
assertEquals("StringArray", sanitizeClassName("String[]"));
assertEquals("intArrayArray", sanitizeClassName("int[][]"));
}

@Test
public void sanitizeClassNameSpecialChars() {
assertEquals("My_Class", sanitizeClassName("My$Class"));
assertEquals("My_Class_2", sanitizeClassName("My-Class-2"));
assertEquals("My_ClassArray", sanitizeClassName("My$Class[]"));
}
}
Loading