Skip to content

Commit d499f65

Browse files
authored
fix(core): avoid json-format output invoking constructors. #3111 (#3116)
fastjson2 may create a default instance via no-arg constructor when building writers, which can trigger side effects (e.g. singleton guards) during `options json-format true`. Use a custom ObjectWriterProvider that disables default value instantiation and share the JSON config via ObjectView.toJsonString(), reused by McpObjectVOFilter. Fixes #3111
1 parent 78abea7 commit d499f65

File tree

3 files changed

+61
-11
lines changed

3 files changed

+61
-11
lines changed

core/src/main/java/com/taobao/arthas/core/mcp/util/McpObjectVOFilter.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package com.taobao.arthas.core.mcp.util;
22

3-
import com.alibaba.fastjson2.JSON;
4-
import com.alibaba.fastjson2.JSONFactory;
5-
import com.alibaba.fastjson2.JSONWriter;
63
import com.alibaba.fastjson2.filter.ValueFilter;
74
import com.taobao.arthas.core.GlobalOptions;
85
import com.taobao.arthas.core.command.model.ObjectVO;
@@ -93,13 +90,7 @@ private String drawObjectView(ObjectVO objectVO) {
9390
*/
9491
private String drawJsonView(Object object) {
9592
try {
96-
JSONWriter.Context context = JSONFactory.createWriteContext();
97-
context.setMaxLevel(4097);
98-
context.config(JSONWriter.Feature.IgnoreErrorGetter, true);
99-
context.config(JSONWriter.Feature.ReferenceDetection, true);
100-
context.config(JSONWriter.Feature.IgnoreNonFieldGetter, true);
101-
context.config(JSONWriter.Feature.WriteNonStringKeyAsString, true);
102-
return JSON.toJSONString(object, context);
93+
return ObjectView.toJsonString(object);
10394
} catch (Exception e) {
10495
logger.debug("ObjectView-style serialization failed, using toString: {}", e.getMessage());
10596
return objectToString(object);

core/src/main/java/com/taobao/arthas/core/view/ObjectView.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
55
import com.alibaba.fastjson2.JSON;
66
import com.alibaba.fastjson2.JSONWriter;
7+
import com.alibaba.fastjson2.writer.FieldWriter;
8+
import com.alibaba.fastjson2.writer.ObjectWriterCreator;
9+
import com.alibaba.fastjson2.writer.ObjectWriterProvider;
710
import com.taobao.arthas.common.ArthasConstants;
811
import com.taobao.arthas.core.GlobalOptions;
912
import com.taobao.arthas.core.command.model.ObjectVO;
@@ -25,6 +28,25 @@ public class ObjectView implements View {
2528
public static final int MAX_DEEP = 4;
2629
private static final Logger logger = LoggerFactory.getLogger(ObjectView.class);
2730
private final static int MAX_OBJECT_LENGTH = ArthasConstants.MAX_HTTP_CONTENT_LENGTH;
31+
private static final ObjectWriterProvider JSON_OBJECT_WRITER_PROVIDER = new ObjectWriterProvider(
32+
new ObjectWriterCreator() {
33+
@Override
34+
protected void setDefaultValue(List<FieldWriter> fieldWriters, Class objectClass) {
35+
// fastjson2 默认会通过无参构造函数创建一个对象来提取字段默认值(用于 NotWriteDefaultValue 等能力),
36+
// 这可能触发目标对象的构造逻辑(比如单例守卫、资源初始化等),在 Arthas 里属于不可接受的副作用。
37+
// 这里直接禁用该行为,只基于现有对象进行序列化。
38+
}
39+
});
40+
41+
public static String toJsonString(Object object) {
42+
JSONWriter.Context context = new JSONWriter.Context(JSON_OBJECT_WRITER_PROVIDER);
43+
context.setMaxLevel(4097);
44+
context.config(JSONWriter.Feature.IgnoreErrorGetter,
45+
JSONWriter.Feature.ReferenceDetection,
46+
JSONWriter.Feature.IgnoreNonFieldGetter,
47+
JSONWriter.Feature.WriteNonStringKeyAsString);
48+
return JSON.toJSONString(object, context);
49+
}
2850

2951
private final Object object;
3052
private final int deep;
@@ -54,7 +76,7 @@ public String draw() {
5476
StringBuilder buf = new StringBuilder();
5577
try {
5678
if (GlobalOptions.isUsingJson) {
57-
return JSON.toJSONString(object, JSONWriter.Feature.IgnoreErrorGetter);
79+
return toJsonString(object);
5880
}
5981
renderObject(object, 0, deep, buf);
6082
return buf.toString();

core/src/test/java/com/taobao/arthas/core/view/ObjectViewTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.taobao.arthas.core.view;
22

3+
import com.taobao.arthas.core.GlobalOptions;
34
import org.junit.Assert;
45
import org.junit.Test;
56

@@ -206,6 +207,22 @@ public void testEnumList() {
206207
Assert.assertEquals(expected, objectView.draw());
207208
}
208209

210+
@Test
211+
public void testJsonFormatDoNotCreateNewInstance() {
212+
boolean old = GlobalOptions.isUsingJson;
213+
try {
214+
JsonFormatSingleton singleton = JsonFormatSingleton.getInstance();
215+
GlobalOptions.isUsingJson = true;
216+
217+
ObjectView objectView = new ObjectView(new Object[] { singleton }, 3);
218+
String output = objectView.draw();
219+
Assert.assertFalse(output.startsWith("ERROR DATA!!!"));
220+
Assert.assertEquals(1, JsonFormatSingleton.constructorCalls);
221+
} finally {
222+
GlobalOptions.isUsingJson = old;
223+
}
224+
}
225+
209226
@Test
210227
public void testDate() {
211228
Date d = new Date(1531204354961L - TimeZone.getDefault().getRawOffset()
@@ -324,4 +341,24 @@ public void setJ(String j) {
324341
public enum EnumDemo {
325342
DEMO;
326343
}
344+
345+
public static class JsonFormatSingleton {
346+
private static final JsonFormatSingleton INSTANCE;
347+
private static volatile int constructorCalls = 0;
348+
349+
static {
350+
INSTANCE = new JsonFormatSingleton();
351+
}
352+
353+
private JsonFormatSingleton() {
354+
constructorCalls++;
355+
if (constructorCalls > 1) {
356+
throw new IllegalStateException("JsonFormatSingleton is created!");
357+
}
358+
}
359+
360+
public static JsonFormatSingleton getInstance() {
361+
return INSTANCE;
362+
}
363+
}
327364
}

0 commit comments

Comments
 (0)