-
Notifications
You must be signed in to change notification settings - Fork 5
refactor(framework): replace fastjson with jackson-databind #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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)); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,156 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| package org.tron.json; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.core.JsonParser; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 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}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Swap the import line; no other source changes required for basic usages. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p>All static methods delegate to a shared, thread-safe Jackson {@link ObjectMapper} that is | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * configured to match the lenient parsing behavior historically provided by Fastjson: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * <ul> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * <li>Unquoted field names and single-quoted strings are accepted.</li> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * <li>Unknown properties are ignored (Fastjson default).</li> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * <li>Floating-point numbers are mapped to {@link java.math.BigDecimal} for precision.</li> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * <li>Case-insensitive property matching is enabled.</li> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * </ul> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public final class JSON { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Shared, fully-configured Jackson mapper. Exposed for callers that hold a mapper reference. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public static final ObjectMapper MAPPER = JsonMapper.builder() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .serializationInclusion(JsonInclude.Include.NON_NULL) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| private JSON() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // parseObject | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Parses a JSON object string and returns a {@link JSONObject}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Mirrors {@code JSON.parseObject(String)} and {@code JSONObject.parseObject(String)}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public static JSONObject parseObject(String text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (text == null || text.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return new JSONObject(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return new JSONObject((ObjectNode) MAPPER.readTree(text)); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new JSONException("Failed to parse JSON object: " + e.getMessage(), e); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Parses a JSON string and deserializes it into the given Java type. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Mirrors {@code JSON.parseObject(String, Class)}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public static <T> T parseObject(String text, Class<T> clazz) throws JsonProcessingException { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (text == null || text.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return MAPPER.readValue(text, clazz); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // parse (validate / generic parse — callers typically ignore the return value) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Parses any valid JSON value. Returns {@code null} on failure (mirrors Fastjson behaviour). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Mirrors {@code JSON.parse(String)}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return a Jackson {@link JsonNode} representing the parsed JSON, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * or {@code null} if parsing fails. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public static JsonNode parse(String text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (text == null || text.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return MAPPER.readTree(text); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+81
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The Severity Level: Critical 🚨- ❌ Form-encoded POST /wallet/marketcancelorder requests fail to parse.
- ❌ Other wallet HTTP POSTs with form bodies break parsing.
- ⚠️ UtilMockTest.testGetJsonString unit test fails with bug.
Suggested change
Steps of Reproduction ✅1. Start a full node with HTTP API enabled so `FullNodeHttpApiService` is active (see
`framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java:37-41`).
2. Send an HTTP POST request to `/wallet/marketcancelorder` with header `Content-Type:
application/x-www-form-urlencoded` (endpoint wiring at
`FullNodeHttpApiService.java:58-60`) and a typical form body, e.g.
`owner_address=owner_address&contract_address=contract_address`.
3. The request is handled by `MarketCancelOrderServlet.doPost(...)`
(`framework/src/main/java/org/tron/core/services/http/MarketCancelOrderServlet.java:6-26`),
which calls `PostParams.getPostParams(request)` (`PostParams.java:24-32`). Because the
content type is `APPLICATION_FORM_URLENCODED`, `PostParams.getPostParams` calls
`Util.getJsonString(input)` (`PostParams.java:24-29`).
4. Inside `Util.getJsonString`
(`framework/src/main/java/org/tron/core/services/http/Util.java:614-633`),
`isValidJson(str)` (`Util.java:636-643`) calls `JSON.parse(str)`
(`common/src/main/java/org/tron/json/JSON.java:97-106`). With the current implementation,
`MAPPER.readTree(str)` throws for the URL-encoded string, `JSON.parse` catches the
exception and returns `null` instead of rethrowing, so no exception propagates.
`isValidJson` returns `true`, `getJsonString` returns the original URL-encoded string
instead of converting it to JSON, and `MarketCancelOrderServlet.doPost` then passes this
non-JSON string to `JsonFormat.merge(contract, build, visible)`
(`MarketCancelOrderServlet.java:8-12`), which fails to parse and results in an error
response instead of the previously working behaviour where URL-encoded bodies are
converted to JSON and processed successfully.Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** common/src/main/java/org/tron/json/JSON.java
**Line:** 94:104
**Comment:**
*Logic Error: The `parse(String)` method swallows all parsing exceptions and returns null on failure, which breaks existing callers like `Util.isValidJson` that rely on an exception being thrown to detect invalid JSON; this will cause invalid JSON strings to be treated as valid and skip the intended URL-encoded fallback path.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. |
||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Parses a JSON array string and returns a {@link JSONArray}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Mirrors {@code JSON.parseArray(String)} and {@code JSONArray.parseArray(String)}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public static JSONArray parseArray(String text) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return JSONArray.parseArray(text); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // toJSONString | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Serializes an object to a compact JSON string. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Mirrors {@code JSON.toJSONString(Object)}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public static String toJSONString(Object obj) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (obj == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return "null"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Unwrap our own wrapper types so the inner Jackson node is serialized | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (obj instanceof JSONObject) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return ((JSONObject) obj).unwrap().toString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (obj instanceof JSONArray) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return ((JSONArray) obj).unwrap().toString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return MAPPER.writeValueAsString(obj); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new JSONException("Failed to serialise object: " + e.getMessage(), e); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Serializes an object to a JSON string, optionally pretty-printed. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Mirrors {@code JSON.toJSONString(Object, boolean)}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| public static String toJSONString(Object obj, boolean prettyFormat) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!prettyFormat) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return toJSONString(obj); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (obj == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return "null"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (obj instanceof JSONObject) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return MAPPER.writerWithDefaultPrettyPrinter() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .writeValueAsString(((JSONObject) obj).unwrap()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (obj instanceof JSONArray) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return MAPPER.writerWithDefaultPrettyPrinter() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .writeValueAsString(((JSONArray) obj).unwrap()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new JSONException("Failed to serialise object: " + e.getMessage(), e); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON.parsenow catches all parsing failures and returnsnull, butUtil.isValidJsonstill treats success as “no exception” (framework/src/main/java/org/tron/core/services/http/Util.java). This makes malformed payloads (includingapplication/x-www-form-urlencodedbodies likea=1&b=2) look like valid JSON, soUtil.getJsonStringskips URL-decoding and downstream request parsing fails. Previously, Fastjson surfaced parse errors via exceptions, which this validation path depends on.Useful? React with 👍 / 👎.