Skip to content

Commit 665adfa

Browse files
halibobo1205claudecodeant-ai[bot]chatgpt-codex-connector[bot]
committed
refactor(framework): replace fastjson with jackson
Remove the Fastjson dependency entirely and replace it with Jackson-backed drop-in wrappers (JSON, JSONObject, JSONArray, JSONException) that preserve the same public API surface. Motivation: - Fastjson has a history of critical CVEs and is no longer actively maintained for 1.x - Jackson-databind 2.18.6 addresses CVE GHSA-72hv-8253-57qq Core changes (common module): - Add org.tron.json.{JSON, JSONObject, JSONArray, JSONException} wrappers backed by a shared Jackson ObjectMapper configured to match Fastjson 1.x parsing/serialization defaults: - Unquoted field names and single-quoted strings (lenient parsing) - BigDecimal for floats, case-insensitive property matching - Null fields omitted (matches Fastjson default) - Type-safe accessors: getBoolean/getLong/getDouble/getIntValue/ getLongValue/getBigDecimal throw JSONException on invalid text instead of silently returning 0/false - parseObject(String) guards against ClassCastException on non-object JSON roots; parseArray handles whitespace-only input - parseObject(String, Class) delegates to parseObject/parseArray for wrapper types to avoid silent field loss via ObjectMapper - Upgrade jackson-databind 2.18.3 → 2.18.6 HTTP servlet changes (framework module): - Swap import from com.alibaba.fastjson → org.tron.json across all HTTP API servlets, JSON-RPC layer, and event/log parsers - No changes to request/response JSON structure — existing API contracts are preserved Test changes: - Add BaseHttpTest base class managing Args lifecycle, Wallet mock, MINIMAL_TX constant, and request/response factory methods (postRequest, getRequest, newResponse) - 44 servlet test classes refactored to extend BaseHttpTest, eliminating ~1400 lines of duplicated boilerplate - Strengthen weak assertNotNull checks to content-based assertions: assertTrue(contains("raw_data")) for transaction servlets, assertTrue(contains("blockID")) for block queries, etc. - Add Mockito verify for wallet service calls in query servlets to catch request-to-service mapping regressions - Fix test environment: initialize Args from config-test.conf (maxMessageSize) and use MINIMAL_TX with raw_data to prevent NPE in Util.printCreateTransaction - Add JsonCompatibilityFuzzTest: 500-round fuzz covering round-trip serialization, BigDecimal/BigInteger precision, deep nesting, unicode, and boundary values - Use SecureRandom for fuzz test randomization Build: - Remove fastjson from common/build.gradle dependencies - Update gradle/verification-metadata.xml for jackson 2.18.6 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: codeant-ai[bot] <151821869+codeant-ai[bot]@users.noreply.github.com> Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent 721b165 commit 665adfa

166 files changed

Lines changed: 6466 additions & 276 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ src/test/java/org/tron/consensus2
3636
src/main/java/META-INF/
3737
src/main/resources/META-INF/
3838
/bin/
39+
bin/
3940

4041
# Eclipse IDE specific files and folders
4142
/.project
@@ -57,3 +58,4 @@ Wallet
5758

5859
/framework/propPath
5960
.cache
61+
.claude

actuator/src/main/java/org/tron/core/vm/trace/ProgramTrace.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ public void merge(ProgramTrace programTrace) {
9090
this.ops.addAll(programTrace.ops);
9191
}
9292

93-
public String asJsonString(boolean formatted) {
94-
return serializeFieldsOnly(this, formatted);
93+
public String asJsonString() {
94+
return serializeFieldsOnly(this);
9595
}
9696

9797
@Override
9898
public String toString() {
99-
return asJsonString(true);
99+
return asJsonString();
100100
}
101101
}
Lines changed: 11 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,28 @@
11
package org.tron.core.vm.trace;
22

3-
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4-
import com.fasterxml.jackson.core.JsonGenerator;
5-
import com.fasterxml.jackson.core.JsonProcessingException;
6-
import com.fasterxml.jackson.databind.JsonSerializer;
3+
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
4+
import com.fasterxml.jackson.annotation.PropertyAccessor;
75
import com.fasterxml.jackson.databind.ObjectMapper;
86
import com.fasterxml.jackson.databind.SerializationFeature;
9-
import com.fasterxml.jackson.databind.SerializerProvider;
10-
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
11-
import java.io.IOException;
7+
import com.fasterxml.jackson.databind.json.JsonMapper;
128
import lombok.extern.slf4j.Slf4j;
13-
import org.bouncycastle.util.encoders.Hex;
14-
import org.tron.common.runtime.vm.DataWord;
15-
import org.tron.core.vm.Op;
169

1710
@Slf4j(topic = "VM")
1811
public final class Serializers {
1912

20-
public static String serializeFieldsOnly(Object value, boolean pretty) {
21-
try {
22-
ObjectMapper mapper = createMapper(pretty);
23-
mapper.setVisibilityChecker(fieldsOnlyVisibilityChecker(mapper));
13+
private static final ObjectMapper mapper = JsonMapper.builder()
14+
.enable(SerializationFeature.INDENT_OUTPUT)
15+
.visibility(PropertyAccessor.FIELD, Visibility.ANY)
16+
.visibility(PropertyAccessor.GETTER, Visibility.NONE)
17+
.visibility(PropertyAccessor.IS_GETTER, Visibility.NONE)
18+
.build();
2419

20+
public static String serializeFieldsOnly(Object value) {
21+
try {
2522
return mapper.writeValueAsString(value);
2623
} catch (Exception e) {
2724
logger.error("JSON serialization error: ", e);
2825
return "{}";
2926
}
3027
}
31-
32-
private static VisibilityChecker<?> fieldsOnlyVisibilityChecker(ObjectMapper mapper) {
33-
return mapper.getSerializationConfig().getDefaultVisibilityChecker()
34-
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
35-
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
36-
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE);
37-
}
38-
39-
public static ObjectMapper createMapper(boolean pretty) {
40-
ObjectMapper mapper = new ObjectMapper();
41-
if (pretty) {
42-
mapper.enable(SerializationFeature.INDENT_OUTPUT);
43-
}
44-
return mapper;
45-
}
46-
47-
public static class DataWordSerializer extends JsonSerializer<DataWord> {
48-
49-
@Override
50-
public void serialize(DataWord energy, JsonGenerator jgen, SerializerProvider provider)
51-
throws IOException, JsonProcessingException {
52-
jgen.writeString(energy.value().toString());
53-
}
54-
}
55-
56-
public static class ByteArraySerializer extends JsonSerializer<byte[]> {
57-
58-
@Override
59-
public void serialize(byte[] memory, JsonGenerator jgen, SerializerProvider provider)
60-
throws IOException, JsonProcessingException {
61-
jgen.writeString(Hex.toHexString(memory));
62-
}
63-
}
64-
65-
public static class OpCodeSerializer extends JsonSerializer<Byte> {
66-
67-
@Override
68-
public void serialize(Byte op, JsonGenerator jgen, SerializerProvider provider)
69-
throws IOException, JsonProcessingException {
70-
jgen.writeString(Op.getNameOf(op));
71-
}
72-
}
7328
}

common/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ sourceCompatibility = 1.8
88

99

1010
dependencies {
11-
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.3' // https://github.com/FasterXML/jackson-databind/issues/3627
11+
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.6' // https://github.com/FasterXML/jackson-databind/issues/3627
1212
api "com.cedarsoftware:java-util:3.2.0"
1313
api group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.1'
1414
api group: 'commons-codec', name: 'commons-codec', version: '1.11'

common/src/main/java/org/tron/common/utils/JsonUtil.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package org.tron.common.utils;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.databind.json.JsonMapper;
45
import org.springframework.util.StringUtils;
56

67
public class JsonUtil {
78

9+
private static final ObjectMapper om = new JsonMapper();
10+
811
public static final <T> T json2Obj(String jsonString, Class<T> clazz) {
912
if (!StringUtils.isEmpty(jsonString) && clazz != null) {
1013
try {
11-
ObjectMapper om = new ObjectMapper();
1214
return om.readValue(jsonString, clazz);
1315
} catch (Exception var3) {
1416
throw new RuntimeException(var3);
@@ -22,7 +24,6 @@ public static final String obj2Json(Object obj) {
2224
if (obj == null) {
2325
return null;
2426
} else {
25-
ObjectMapper om = new ObjectMapper();
2627
try {
2728
return om.writeValueAsString(obj);
2829
} catch (Exception var3) {
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package org.tron.json;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.core.JsonParser;
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import com.fasterxml.jackson.databind.MapperFeature;
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.databind.SerializationFeature;
10+
import com.fasterxml.jackson.databind.json.JsonMapper;
11+
import com.fasterxml.jackson.databind.node.ObjectNode;
12+
13+
/**
14+
* Drop-in replacement for {@code com.alibaba.fastjson.JSON}.
15+
* Swap the import line; no other source changes required for basic usages.
16+
*
17+
* <p>All static methods delegate to a shared, thread-safe Jackson {@link ObjectMapper} that is
18+
* configured to match the lenient parsing behavior historically provided by Fastjson:
19+
* <ul>
20+
* <li>Unquoted field names and single-quoted strings are accepted.</li>
21+
* <li>Unknown properties are ignored (Fastjson default).</li>
22+
* <li>Floating-point numbers are mapped to {@link java.math.BigDecimal} for precision.</li>
23+
* <li>Case-insensitive property matching is enabled.</li>
24+
* </ul>
25+
*/
26+
public final class JSON {
27+
28+
/**
29+
* Shared, fully-configured Jackson mapper. Exposed for callers that hold a mapper reference.
30+
*
31+
* <p>Each setting mirrors a Fastjson 1.x default.
32+
*/
33+
public static final ObjectMapper MAPPER = JsonMapper.builder()
34+
// Fastjson Feature.AllowUnQuotedFieldNames (default ON)
35+
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
36+
// Fastjson Feature.AllowSingleQuotes (default ON)
37+
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
38+
// Fastjson tolerates trailing commas (e.g. {"a":1,}) by default
39+
.configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true)
40+
// Fastjson Feature.UseBigDecimal (default ON)
41+
// https://github.com/alibaba/fastjson/wiki/deserialize_disable_bigdecimal_cn
42+
.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
43+
// Fastjson Feature.IgnoreNotMatch (default ON) — unknown fields silently ignored
44+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
45+
// Fastjson serializes empty beans as "{}" without error
46+
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
47+
// Fastjson omits null-valued fields by default (WriteMapNullValue is OFF by default)
48+
// https://github.com/alibaba/fastjson/wiki/WriteNull_cn
49+
.serializationInclusion(JsonInclude.Include.NON_NULL)
50+
// Fastjson uses WriteDateUseDateFormat (string) not timestamps by default
51+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
52+
// Fastjson smart-match: field names are matched ignoring case/underscores by default
53+
// (DisableFieldSmartMatch is OFF by default → smart match ON)
54+
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
55+
.build();
56+
57+
private JSON() {
58+
}
59+
60+
// -------------------------------------------------------------------------
61+
// parseObject
62+
// -------------------------------------------------------------------------
63+
64+
/**
65+
* Parses a JSON object string and returns a {@link JSONObject}.
66+
* Mirrors {@code JSON.parseObject(String)} and {@code JSONObject.parseObject(String)}.
67+
*/
68+
public static JSONObject parseObject(String text) {
69+
if (text == null || text.trim().isEmpty()) {
70+
return null;
71+
}
72+
try {
73+
JsonNode node = MAPPER.readTree(text);
74+
if (node == null || node.isNull()) {
75+
return null;
76+
}
77+
if (!node.isObject()) {
78+
throw new JSONException("Expected JSON object but got: " + node.getNodeType());
79+
}
80+
return new JSONObject((ObjectNode) node);
81+
} catch (JSONException e) {
82+
throw e;
83+
} catch (Exception e) {
84+
throw new JSONException("Failed to parse JSON object: " + e.getMessage(), e);
85+
}
86+
}
87+
88+
/**
89+
* Parses a JSON string and deserializes it into the given Java type.
90+
* Mirrors {@code JSON.parseObject(String, Class)}.
91+
*/
92+
public static <T> T parseObject(String text, Class<T> clazz) {
93+
if (text == null || text.trim().isEmpty()) {
94+
return null;
95+
}
96+
if (clazz == JSONObject.class) {
97+
return clazz.cast(parseObject(text));
98+
}
99+
if (clazz == JSONArray.class) {
100+
return clazz.cast(parseArray(text));
101+
}
102+
try {
103+
return MAPPER.readValue(text, clazz);
104+
} catch (Exception e) {
105+
throw new JSONException("Failed to parse JSON object: " + e.getMessage(), e);
106+
}
107+
}
108+
109+
// -------------------------------------------------------------------------
110+
// parse (validate / generic parse — callers typically ignore the return value)
111+
// -------------------------------------------------------------------------
112+
113+
/**
114+
* Parses any valid JSON value. Throws {@link JSONException} on invalid input
115+
* so callers can detect malformed JSON (e.g. via try/catch), mirroring
116+
* Fastjson's observable behaviour in this codebase.
117+
* Mirrors {@code JSON.parse(String)}.
118+
*/
119+
public static JsonNode parse(String text) {
120+
if (text == null || text.trim().isEmpty()) {
121+
return null;
122+
}
123+
try {
124+
return MAPPER.readTree(text);
125+
} catch (Exception e) {
126+
throw new JSONException("Failed to parse JSON: " + e.getMessage(), e);
127+
}
128+
}
129+
130+
/**
131+
* Parses a JSON array string and returns a {@link JSONArray}.
132+
* Mirrors {@code JSON.parseArray(String)} and {@code JSONArray.parseArray(String)}.
133+
*/
134+
public static JSONArray parseArray(String text) {
135+
return JSONArray.parseArray(text);
136+
}
137+
138+
// -------------------------------------------------------------------------
139+
// toJSONString
140+
// -------------------------------------------------------------------------
141+
142+
/**
143+
* Serializes an object to a compact JSON string.
144+
* Mirrors {@code JSON.toJSONString(Object)}.
145+
*/
146+
public static String toJSONString(Object obj) {
147+
if (obj == null) {
148+
return "null";
149+
}
150+
try {
151+
// Unwrap our own wrapper types so the inner Jackson node is serialized
152+
if (obj instanceof JSONObject) {
153+
return MAPPER.writeValueAsString(((JSONObject) obj).unwrap());
154+
}
155+
if (obj instanceof JSONArray) {
156+
return MAPPER.writeValueAsString(((JSONArray) obj).unwrap());
157+
}
158+
return MAPPER.writeValueAsString(obj);
159+
} catch (Exception e) {
160+
throw new JSONException("Failed to serialise object: " + e.getMessage(), e);
161+
}
162+
}
163+
164+
/**
165+
* Serializes an object to a JSON string, optionally pretty-printed.
166+
* Mirrors {@code JSON.toJSONString(Object, boolean)}.
167+
*/
168+
public static String toJSONString(Object obj, boolean prettyFormat) {
169+
if (!prettyFormat) {
170+
return toJSONString(obj);
171+
}
172+
if (obj == null) {
173+
return "null";
174+
}
175+
try {
176+
if (obj instanceof JSONObject) {
177+
return MAPPER.writerWithDefaultPrettyPrinter()
178+
.writeValueAsString(((JSONObject) obj).unwrap());
179+
}
180+
if (obj instanceof JSONArray) {
181+
return MAPPER.writerWithDefaultPrettyPrinter()
182+
.writeValueAsString(((JSONArray) obj).unwrap());
183+
}
184+
return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
185+
} catch (Exception e) {
186+
throw new JSONException("Failed to serialise object: " + e.getMessage(), e);
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)