Skip to content

Commit 9529fb8

Browse files
authored
feat(framework,actuator,common): replace fastjson with jackson (#6701)
* feat(framework,actuator,common): replace fastjson with jackson Replace `com.alibaba:fastjson` with Jackson-backed drop-in wrappers (`org.tron.json.{JSON, JSONObject, JSONArray, JSONException}`). No external API changes — all HTTP and JSON-RPC responses remain identical. Motivation: - Fastjson 1.2.83 is EOL with 20+ CVEs including critical RCE - Upgrade jackson-databind 2.18.3 → 2.18.6 (GHSA-72hv-8253-57qq) - Unify JSON handling (previously split between Jackson and Fastjson) Core changes (common): - Add org.tron.json wrappers backed by a shared ObjectMapper - Remove fastjson from common/build.gradle HTTP & servlet changes (framework): - Swap imports from com.alibaba.fastjson → org.tron.json across all HTTP servlets, JSON-RPC layer, and event/log parsers Test changes: - Add BaseHttpTest base class for servlet test lifecycle Build: - Update jackson to 2.18.6 - Remove fastjson close #6607 * fix(test): remove trailing whitespace in GetDelegatedResourceAccountIndexV2ServletTest * test(framework): add buildTransaction test * refactor(common): tighten TypeUtils visibility and drop unused casts * refactor(common): mark org.tron.json wrappers as @deprecated * refactor(common): drop unused JSONException(Throwable) constructor * test(framework): expand org.tron.json wrapper test coverage * feat(common,framework): cap JSON parse depth and token count * fix(common): match Fastjson 1.x NaN/Infinity/NULL behavior * fix(json): align jackson parser with fastjson defaults * test(json): remove Fastjson * fix(json): reject unsupported fastjson edge cases * test(json): cover duplicate field names * fix(json): retain single trailing comma support * refactor(trace): inline program trace json serialization * fix(test): remove fastjson * fix(test): use toLowerCase(Locale.ROOT)
1 parent fe25380 commit 9529fb8

173 files changed

Lines changed: 4268 additions & 326 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.

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

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

93-
public String asJsonString(boolean formatted) {
94-
return serializeFieldsOnly(this, formatted);
95-
}
96-
9793
@Override
9894
public String toString() {
99-
return asJsonString(true);
95+
return serializeFieldsOnly(this);
10096
}
10197
}
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/parameter/CommonParameter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,12 @@ public class CommonParameter {
513513
public int pBFTHttpPort;
514514
@Getter
515515
@Setter
516+
public int maxNestingDepth = 100;
517+
@Getter
518+
@Setter
519+
public int maxTokenCount = 100_000;
520+
@Getter
521+
@Setter
516522
public long pBFTExpireNum; // clearParam: 20
517523
@Getter
518524
@Setter

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) {

common/src/main/java/org/tron/core/config/args/NodeConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ public static class HttpConfig {
203203
private boolean solidityEnable = true;
204204
private int solidityPort = 8091;
205205
private long maxMessageSize = 4194304;
206+
private int maxNestingDepth = 100;
207+
private int maxTokenCount = 100_000;
206208
// PBFT fields — handled manually (same naming issue as CommitteeConfig)
207209
// Default must match CommonParameter.pBFTHttpEnable = true
208210
@Getter(lombok.AccessLevel.NONE)
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package org.tron.json;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.core.JsonFactory;
5+
import com.fasterxml.jackson.core.StreamReadConstraints;
6+
import com.fasterxml.jackson.core.json.JsonReadFeature;
7+
import com.fasterxml.jackson.databind.DeserializationFeature;
8+
import com.fasterxml.jackson.databind.JsonNode;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.databind.SerializationFeature;
11+
import com.fasterxml.jackson.databind.json.JsonMapper;
12+
import com.fasterxml.jackson.databind.node.ObjectNode;
13+
import org.tron.common.parameter.CommonParameter;
14+
15+
/**
16+
* Drop-in replacement for {@code com.alibaba.fastjson.JSON}.
17+
*
18+
* @deprecated Compatibility shim from the fastjson removal. New code should use
19+
* Jackson directly ({@link com.fasterxml.jackson.databind.ObjectMapper},
20+
* {@link com.fasterxml.jackson.databind.JsonNode}) instead of this helper.
21+
*/
22+
@Deprecated
23+
public final class JSON {
24+
25+
// Initialization-order invariant: this class must NOT be loaded before
26+
// Args.setParam() completes. The factory's StreamReadConstraints are a
27+
// one-shot snapshot of CommonParameter at class-init time. If JSON is
28+
// touched too early — e.g. a stray reference in startup code or in a static
29+
// initializer that runs before Args — the snapshot captures CommonParameter's
30+
// hardcoded defaults (100 / 100_000) and any user override of
31+
// node.http.maxNestingDepth / maxTokenCount is silently ignored.
32+
// Current production startup (FullNode.main) calls Args.setParam first and
33+
// no path in that call chain references this class, so the invariant holds.
34+
static final ObjectMapper MAPPER = JsonMapper.builder(buildFactory())
35+
// Fastjson Feature.AllowUnQuotedFieldNames (default ON)
36+
.enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES)
37+
// Fastjson Feature.AllowSingleQuotes (default ON)
38+
.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
39+
// Partial compatibility with Fastjson Feature.AllowArbitraryCommas:
40+
// this only covers a single trailing comma like {"a":1,} or [1,2,].
41+
// Repeated/arbitrary commas like {"a":1,,,,} and [1,,2] remain rejected.
42+
.enable(JsonReadFeature.ALLOW_TRAILING_COMMA)
43+
// Fastjson accepts a leading plus sign for numbers (for example +123, +0.5)
44+
.enable(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS)
45+
// Partial compatibility for Fastjson's asymmetric decimal behavior:
46+
// Fastjson accepts +.5 but rejects .5 by default. Jackson cannot model only
47+
// the signed form, so enabling this also accepts .5.
48+
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
49+
// Fastjson accepts a trailing decimal point for numbers (for example 5.)
50+
.enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS)
51+
// Fastjson accepts leading zeros for numbers (for example 007)
52+
.enable(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS)
53+
// Fastjson accepts unescaped control chars in strings (for example raw tab/newline)
54+
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
55+
// Fastjson accepts Java-style comments (// and /* */)
56+
.enable(JsonReadFeature.ALLOW_JAVA_COMMENTS)
57+
// Fastjson Feature.UseBigDecimal (default ON)
58+
// https://github.com/alibaba/fastjson/wiki/deserialize_disable_bigdecimal_cn
59+
.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
60+
// Fastjson Feature.IgnoreNotMatch (default ON) — unknown fields silently ignored
61+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
62+
// Fastjson serializes empty beans as "{}" without error
63+
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
64+
// Fastjson omits null-valued fields by default (WriteMapNullValue is OFF by default)
65+
// https://github.com/alibaba/fastjson/wiki/WriteNull_cn
66+
.serializationInclusion(JsonInclude.Include.NON_NULL)
67+
.build();
68+
69+
private static JsonFactory buildFactory() {
70+
CommonParameter p = CommonParameter.getInstance();
71+
return JsonFactory.builder().streamReadConstraints(StreamReadConstraints.builder()
72+
.maxNestingDepth(p.getMaxNestingDepth()).maxTokenCount(p.getMaxTokenCount())
73+
.build()).build();
74+
}
75+
76+
private JSON() {
77+
}
78+
79+
/**
80+
* Returns {@code true} when {@code text} is null, blank, or a
81+
* lowercase {@code "null"} literal.
82+
*/
83+
static boolean isNullLiteral(String text) {
84+
if (text == null) {
85+
return true;
86+
}
87+
String trimmed = text.trim();
88+
return trimmed.isEmpty() || "null".equals(trimmed);
89+
}
90+
91+
public static JSONObject parseObject(String text) {
92+
if (isNullLiteral(text)) {
93+
return null;
94+
}
95+
try {
96+
JsonNode node = MAPPER.readTree(text);
97+
if (node == null || node.isNull()) {
98+
return null;
99+
}
100+
if (!node.isObject()) {
101+
throw new JSONException("can not cast to JSONObject.");
102+
}
103+
return new JSONObject((ObjectNode) node);
104+
} catch (JSONException e) {
105+
throw e;
106+
} catch (Exception e) {
107+
throw new JSONException(e.getMessage(), e);
108+
}
109+
}
110+
111+
public static JsonNode parse(String text) {
112+
if (isNullLiteral(text)) {
113+
return null;
114+
}
115+
try {
116+
JsonNode node = MAPPER.readTree(text);
117+
if (node == null || node.isNull()) {
118+
return null;
119+
}
120+
return node;
121+
} catch (JSONException e) {
122+
throw e;
123+
} catch (Exception e) {
124+
throw new JSONException(e.getMessage(), e);
125+
}
126+
}
127+
128+
static JSONArray parseArray(String text) {
129+
return JSONArray.parseArray(text);
130+
}
131+
132+
public static String toJSONString(Object obj) {
133+
return toJSONString(obj, false);
134+
}
135+
136+
public static String toJSONString(Object obj, boolean pretty) {
137+
if (obj == null) {
138+
return "null";
139+
}
140+
try {
141+
if (obj instanceof JSONObject) {
142+
return pretty ? MAPPER.writerWithDefaultPrettyPrinter()
143+
.writeValueAsString(((JSONObject) obj).unwrap())
144+
: MAPPER.writeValueAsString(((JSONObject) obj).unwrap());
145+
}
146+
if (obj instanceof JSONArray) {
147+
return pretty ? MAPPER.writerWithDefaultPrettyPrinter()
148+
.writeValueAsString(((JSONArray) obj).unwrap())
149+
: MAPPER.writeValueAsString(((JSONArray) obj).unwrap());
150+
}
151+
return pretty ? MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj)
152+
: MAPPER.writeValueAsString(obj);
153+
} catch (Exception e) {
154+
throw new JSONException(e.getMessage(), e);
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)