Skip to content
Open
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 @@ -90,12 +90,12 @@ public void merge(ProgramTrace programTrace) {
this.ops.addAll(programTrace.ops);
}

public String asJsonString(boolean formatted) {
return serializeFieldsOnly(this, formatted);
public String asJsonString() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] Avoid breaking ProgramTrace.asJsonString(boolean) API: replacing it with a no-arg method introduces source/binary compatibility risk for external callers and removes compact-vs-pretty output control. Prefer keeping the original signature (or adding an overload) to preserve compatibility.

return serializeFieldsOnly(this);
}

@Override
public String toString() {
return asJsonString(true);
return asJsonString();
}
}
67 changes: 11 additions & 56 deletions actuator/src/main/java/org/tron/core/vm/trace/Serializers.java
Original file line number Diff line number Diff line change
@@ -1,73 +1,28 @@
package org.tron.core.vm.trace;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import java.io.IOException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.tron.common.runtime.vm.DataWord;
import org.tron.core.vm.Op;

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

public static String serializeFieldsOnly(Object value, boolean pretty) {
try {
ObjectMapper mapper = createMapper(pretty);
mapper.setVisibilityChecker(fieldsOnlyVisibilityChecker(mapper));
private static final ObjectMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.visibility(PropertyAccessor.FIELD, Visibility.ANY)
.visibility(PropertyAccessor.GETTER, Visibility.NONE)
.visibility(PropertyAccessor.IS_GETTER, Visibility.NONE)
.build();

public static String serializeFieldsOnly(Object value) {
try {
return mapper.writeValueAsString(value);
} catch (Exception e) {
logger.error("JSON serialization error: ", e);
return "{}";
}
}

private static VisibilityChecker<?> fieldsOnlyVisibilityChecker(ObjectMapper mapper) {
return mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE);
}

public static ObjectMapper createMapper(boolean pretty) {
ObjectMapper mapper = new ObjectMapper();
if (pretty) {
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
return mapper;
}

public static class DataWordSerializer extends JsonSerializer<DataWord> {

@Override
public void serialize(DataWord energy, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(energy.value().toString());
}
}

public static class ByteArraySerializer extends JsonSerializer<byte[]> {

@Override
public void serialize(byte[] memory, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(Hex.toHexString(memory));
}
}

public static class OpCodeSerializer extends JsonSerializer<Byte> {

@Override
public void serialize(Byte op, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(Op.getNameOf(op));
}
}
}
2 changes: 1 addition & 1 deletion common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sourceCompatibility = 1.8


dependencies {
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.3' // https://github.com/FasterXML/jackson-databind/issues/3627
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.6' // https://github.com/FasterXML/jackson-databind/issues/3627
api "com.cedarsoftware:java-util:3.2.0"
api group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.1'
api group: 'commons-codec', name: 'commons-codec', version: '1.11'
Expand Down
5 changes: 3 additions & 2 deletions common/src/main/java/org/tron/common/utils/JsonUtil.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package org.tron.common.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.util.StringUtils;

public class JsonUtil {

private static final ObjectMapper om = new JsonMapper();
Comment thread
waynercheung marked this conversation as resolved.

public static final <T> T json2Obj(String jsonString, Class<T> clazz) {
if (!StringUtils.isEmpty(jsonString) && clazz != null) {
try {
ObjectMapper om = new ObjectMapper();
return om.readValue(jsonString, clazz);
} catch (Exception var3) {
throw new RuntimeException(var3);
Expand All @@ -22,7 +24,6 @@ public static final String obj2Json(Object obj) {
if (obj == null) {
return null;
} else {
ObjectMapper om = new ObjectMapper();
try {
return om.writeValueAsString(obj);
} catch (Exception var3) {
Expand Down
155 changes: 155 additions & 0 deletions common/src/main/java/org/tron/json/JSON.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package org.tron.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
* Drop-in replacement for {@code com.alibaba.fastjson.JSON}.
*/
public final class JSON {

public static final ObjectMapper MAPPER = JsonMapper.builder()
// Fastjson Feature.AllowUnQuotedFieldNames (default ON)
.enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES)
// Fastjson Feature.AllowSingleQuotes (default ON)
.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
// Fastjson tolerates trailing commas (e.g. {"a":1,}) by default
.enable(JsonReadFeature.ALLOW_TRAILING_COMMA)
// Fastjson accepts NaN/Infinity as valid tokens
.enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MUST] Please remove this flag from the shared request parser.

This is reachable on current wallet HTTP paths, not just a theoretical Fastjson-parity issue. DeployContractServlet and TriggerSmartContractServlet read call_value, call_token_value, token_id, and fee_limit through Util.getJsonLongValue(...).

With this flag enabled, Infinity / -Infinity parse successfully, getBigDecimal(...) returns null, and those non-required fields become 0L instead of rejecting the request. Before this PR, Fastjson rejected those payloads during parse.

// Fastjson accepts leading plus sign for numbers (e.g. +123)
.enable(JsonReadFeature.ALLOW_LEADING_PLUS_SIGN_FOR_NUMBERS)
// Fastjson accepts leading decimal point for numbers (e.g. .5)
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
// Fastjson accepts trailing decimal point for numbers (e.g. 5.)
.enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS)
// Fastjson accepts leading zeros for numbers (e.g. 007)
.enable(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS)
// Fastjson accepts unescaped control chars in strings (e.g. raw tab/newline)
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
// Fastjson accepts backslash-escaping any character (e.g. \q → q)
.enable(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)
// Fastjson accepts Java-style comments (// and /* */)
.enable(JsonReadFeature.ALLOW_JAVA_COMMENTS)
// Fastjson Feature.UseBigDecimal (default ON)
// https://github.com/alibaba/fastjson/wiki/deserialize_disable_bigdecimal_cn
.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
// Fastjson Feature.IgnoreNotMatch (default ON) — unknown fields silently ignored
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
// Fastjson serializes empty beans as "{}" without error
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
// Fastjson omits null-valued fields by default (WriteMapNullValue is OFF by default)
// https://github.com/alibaba/fastjson/wiki/WriteNull_cn
.serializationInclusion(JsonInclude.Include.NON_NULL)
// Fastjson uses WriteDateUseDateFormat (string) not timestamps by default
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
// Fastjson smart-match: field names are matched ignoring case/underscores by default
// (DisableFieldSmartMatch is OFF by default → smart match ON)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.build();

private JSON() {
}

/**
* Returns {@code true} when {@code text} is null, blank, or a
* case-insensitive {@code "null"} literal — mirroring Fastjson's lenient
* treatment of these inputs as JSON {@code null}.
*/
static boolean isNullLiteral(String text) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MUST] Preserve unquoted NULL compatibility: {a:NULL} currently throws, but fastjson 1.2.83 accepted this and treated it as null. Since this PR claims drop-in behavior, this is a breaking compatibility regression for existing clients and should be fixed before merge.

if (text == null) {
return true;
}
String trimmed = text.trim();
return trimmed.isEmpty() || "null".equalsIgnoreCase(trimmed);
}

public static JSONObject parseObject(String text) {
if (isNullLiteral(text)) {
return null;
}
try {
JsonNode node = MAPPER.readTree(text);
if (node == null || node.isNull()) {
return null;
}
if (!node.isObject()) {
throw new JSONException("can not cast to JSONObject.");
}
return new JSONObject((ObjectNode) node);
} catch (JSONException e) {
throw e;
} catch (Exception e) {
throw new JSONException(e.getMessage(), e);
}
}

public static <T> T parseObject(String text, Class<T> clazz) {
if (isNullLiteral(text)) {
return null;
}
if (clazz == JSONObject.class) {
return clazz.cast(parseObject(text));
}
if (clazz == JSONArray.class) {
return clazz.cast(parseArray(text));
}
try {
return MAPPER.readValue(text, clazz);
} catch (Exception e) {
throw new JSONException(e.getMessage(), e);
}
}

public static JsonNode parse(String text) {
if (isNullLiteral(text)) {
return null;
}
try {
JsonNode node = MAPPER.readTree(text);
if (node == null || node.isNull()) {
return null;
}
return node;
} catch (Exception e) {
throw new JSONException(e.getMessage(), e);
}
}

static JSONArray parseArray(String text) {
return JSONArray.parseArray(text);
}

public static String toJSONString(Object obj) {
return toJSONString(obj, false);
}

public static String toJSONString(Object obj, boolean pretty) {
if (obj == null) {
return "null";
}
try {
if (obj instanceof JSONObject) {
return pretty ? MAPPER.writerWithDefaultPrettyPrinter()
.writeValueAsString(((JSONObject) obj).unwrap())
: MAPPER.writeValueAsString(((JSONObject) obj).unwrap());
}
if (obj instanceof JSONArray) {
return pretty ? MAPPER.writerWithDefaultPrettyPrinter()
.writeValueAsString(((JSONArray) obj).unwrap())
: MAPPER.writeValueAsString(((JSONArray) obj).unwrap());
}
return pretty ? MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj)
: MAPPER.writeValueAsString(obj);
} catch (Exception e) {
throw new JSONException(e.getMessage(), e);
}
}
}
Loading
Loading