diff --git a/core/pom.xml b/core/pom.xml
index 69c39b47e04..7bbe0004b4b 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -211,6 +211,10 @@
ognl
ognl
+
+ com.alibaba
+ QLExpress4
+
org.junit.vintage
junit-vintage-engine
@@ -263,6 +267,10 @@
provided
true
+
+ ognl
+ ognl
+
diff --git a/core/src/main/java/com/taobao/arthas/core/GlobalOptions.java b/core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
index f9b9f0df36c..62ae94e288d 100644
--- a/core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
+++ b/core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
@@ -142,6 +142,26 @@ public class GlobalOptions {
description = STRICT_MESSAGE
)
public static volatile boolean strict = true;
+ /**
+ * 是否切换使用表达式ognl/qlexpress开关
+ */
+ @Option(level = 1,
+ name = "express-type",
+ summary = "Option to use ognl/qlexpress",
+ description = "Option to use ognl/qlexpress in commands, default ognl, can change to qlexpress"
+ )
+ public static volatile String ExpressType = "ognl";
+
+
+ /**
+ * qlexpress使用参数
+ */
+ @Option(level = 1,
+ name = "qlexpress-config",
+ summary = "config init when use qlexpress, with json-simple, for example: {\"precise\": true }",
+ description = ""
+ )
+ public static volatile String QLExpressConfig = "";
public static void updateOnglStrict(boolean strict) {
try {
diff --git a/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java b/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java
index d2d578bc17f..207f7aac848 100644
--- a/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java
+++ b/core/src/main/java/com/taobao/arthas/core/command/express/ExpressFactory.java
@@ -1,5 +1,9 @@
package com.taobao.arthas.core.command.express;
+import com.taobao.arthas.core.GlobalOptions;
+import com.taobao.arthas.core.command.model.ExpressTypeEnum;
+
+
/**
* ExpressFactory
* @author ralf0131 2017-01-04 14:40.
@@ -7,12 +11,8 @@
*/
public class ExpressFactory {
- private static final ThreadLocal expressRef = new ThreadLocal() {
- @Override
- protected Express initialValue() {
- return new OgnlExpress();
- }
- };
+ private static final ThreadLocal expressRef = ThreadLocal.withInitial(() -> new OgnlExpress());
+ private static final ThreadLocal expressRefQLExpress = ThreadLocal.withInitial(() -> new QLExpress());
/**
* get ThreadLocal Express Object
@@ -20,10 +20,23 @@ protected Express initialValue() {
* @return
*/
public static Express threadLocalExpress(Object object) {
+ if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
+ return expressRefQLExpress.get().reset().bind(object);
+ }
return expressRef.get().reset().bind(object);
}
public static Express unpooledExpress(ClassLoader classloader) {
+ if (classloader == null) {
+ classloader = ClassLoader.getSystemClassLoader();
+ }
+ if (GlobalOptions.ExpressType.equals(ExpressTypeEnum.QLEXPRESS.getExpressType())) {
+ return new QLExpress(new QLExpressClassLoaderClassResolver(classloader));
+ }
+ return new OgnlExpress(new ClassLoaderClassResolver(classloader));
+ }
+
+ public static Express unpooledExpressByOGNL(ClassLoader classloader) {
if (classloader == null) {
classloader = ClassLoader.getSystemClassLoader();
}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/express/QLExpress.java b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpress.java
new file mode 100644
index 00000000000..5c4da297397
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpress.java
@@ -0,0 +1,109 @@
+package com.taobao.arthas.core.command.express;
+
+import com.alibaba.arthas.deps.org.slf4j.Logger;
+import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.qlexpress4.ClassSupplier;
+import com.alibaba.qlexpress4.Express4Runner;
+import com.alibaba.qlexpress4.InitOptions;
+import com.alibaba.qlexpress4.QLOptions;
+import com.alibaba.qlexpress4.runtime.ReflectLoader;
+import com.alibaba.qlexpress4.security.QLSecurityStrategy;
+import com.taobao.arthas.core.GlobalOptions;
+import com.taobao.arthas.core.command.model.QLExpressConfigModel;
+
+
+/**
+ * @Author TaoKan
+ * @Date 2024/9/17 6:01 PM
+ */
+public class QLExpress implements Express {
+ private static final Logger logger = LoggerFactory.getLogger(QLExpress.class);
+ private Express4Runner expressRunner;
+ private QLGlobalContext qlGlobalContext;
+
+ private QLOptions qlOptions;
+
+ private InitOptions initOptions;
+
+ public QLExpress() {
+ this(QLExpressCustomClassResolver.customClassResolver);
+ }
+
+ public QLExpress(ClassSupplier classResolver) {
+ initQLExpress(classResolver);
+ initConfig();
+ initContext();
+ }
+
+ private void initConfig() {
+ try {
+ if (GlobalOptions.QLExpressConfig.length() > 0) {
+ QLOptions.Builder qlOptionsBuilder = QLOptions.builder();
+ QLExpressConfigModel qlExpressConfigModel = JSON.parseObject(GlobalOptions.QLExpressConfig, QLExpressConfigModel.class);
+ qlOptionsBuilder.cache(qlExpressConfigModel.isCache());
+ qlOptionsBuilder.avoidNullPointer(qlExpressConfigModel.isAvoidNullPointer());
+ qlOptionsBuilder.maxArrLength(qlExpressConfigModel.getMaxArrLength());
+ qlOptionsBuilder.polluteUserContext(qlExpressConfigModel.isPolluteUserContext());
+ qlOptionsBuilder.precise(qlExpressConfigModel.isPrecise());
+ qlOptionsBuilder.timeoutMillis(qlExpressConfigModel.getTimeoutMillis());
+ qlOptions = qlOptionsBuilder.build();
+ }else {
+ qlOptions = QLOptions.DEFAULT_OPTIONS;
+ }
+ //4.0设置InitOptions
+ }catch (Throwable t){
+ //异常不设置options
+ logger.error("Error Init Options For QLExpress:", t);
+ }
+ }
+
+ private void initQLExpress(ClassSupplier classResolver) {
+ InitOptions.Builder initOptionsBuilder = InitOptions.builder();
+ initOptionsBuilder.securityStrategy(QLSecurityStrategy.open());
+ initOptionsBuilder.allowPrivateAccess(true);
+ initOptionsBuilder.classSupplier(classResolver);
+ initOptions = initOptionsBuilder.build();
+ expressRunner = QLExpressRunner.getInstance(initOptions);
+ }
+
+ private void initContext() {
+ ReflectLoader reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.getExtensionFunctions(), initOptions.isAllowPrivateAccess());
+ qlGlobalContext = new QLGlobalContext(reflectLoader);
+ }
+
+ @Override
+ public Object get(String express) throws ExpressException {
+ try {
+ Object result = expressRunner.execute(express, qlGlobalContext, qlOptions);
+ return result;
+ } catch (Exception e) {
+ logger.error("Error during evaluating the expression with QLExpress:", e);
+ throw new ExpressException(express, e);
+ }
+ }
+
+ @Override
+ public boolean is(String express) throws ExpressException {
+ final Object ret = get(express);
+ return ret instanceof Boolean && (Boolean) ret;
+ }
+
+ @Override
+ public Express bind(Object object) {
+ qlGlobalContext.bindObj(object);
+ return this;
+ }
+
+ @Override
+ public Express bind(String name, Object value) {
+ qlGlobalContext.put(name, value);
+ return this;
+ }
+
+ @Override
+ public Express reset() {
+ qlGlobalContext.clear();
+ return this;
+ }
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressClassLoaderClassResolver.java b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressClassLoaderClassResolver.java
new file mode 100644
index 00000000000..70a469c3b63
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressClassLoaderClassResolver.java
@@ -0,0 +1,40 @@
+package com.taobao.arthas.core.command.express;
+
+import com.alibaba.qlexpress4.ClassSupplier;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Author TaoKan
+ * @Date 2024/12/1 7:07 PM
+ */
+public class QLExpressClassLoaderClassResolver implements ClassSupplier {
+
+ private ClassLoader classLoader;
+
+ private final Map>> cache = new ConcurrentHashMap<>();
+
+ public QLExpressClassLoaderClassResolver(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ private Optional> loadClsInner(String clsQualifiedName) {
+ try {
+ Class> aClass = null;
+ if (classLoader != null) {
+ aClass = classLoader.loadClass(clsQualifiedName);
+ }else {
+ aClass = Class.forName(clsQualifiedName);
+ }
+ return Optional.of(aClass);
+ } catch (ClassNotFoundException | NoClassDefFoundError e) {
+ return Optional.empty();
+ }
+ }
+ @Override
+ public Class> loadCls(String className) {
+ Optional> clsOp = cache.computeIfAbsent(className, this::loadClsInner);
+ return clsOp.orElse(null);
+ }
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressCustomClassResolver.java b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressCustomClassResolver.java
new file mode 100644
index 00000000000..9c127fe50e4
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressCustomClassResolver.java
@@ -0,0 +1,44 @@
+package com.taobao.arthas.core.command.express;
+
+import com.alibaba.arthas.deps.org.slf4j.Logger;
+import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
+import com.alibaba.qlexpress4.ClassSupplier;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Author TaoKan
+ * @Date 2024/12/1 7:06 PM
+ */
+public class QLExpressCustomClassResolver implements ClassSupplier {
+
+ public static final QLExpressCustomClassResolver customClassResolver = new QLExpressCustomClassResolver();
+
+ private final Map>> cache = new ConcurrentHashMap<>();
+
+ private QLExpressCustomClassResolver() {
+
+ }
+
+ private Optional> loadClsInner(String clsQualifiedName) {
+ try {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ Class> aClass = null;
+ if (classLoader != null) {
+ aClass = classLoader.loadClass(clsQualifiedName);
+ } else {
+ aClass = Class.forName(clsQualifiedName);
+ }
+ return Optional.of(aClass);
+ } catch (ClassNotFoundException | NoClassDefFoundError e) {
+ return Optional.empty();
+ }
+ }
+ @Override
+ public Class> loadCls(String clsQualifiedName) {
+ Optional> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner);
+ return clsOp.orElse(null);
+ }
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressRunner.java b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressRunner.java
new file mode 100644
index 00000000000..c9c18f603b8
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/command/express/QLExpressRunner.java
@@ -0,0 +1,34 @@
+package com.taobao.arthas.core.command.express;
+
+
+import com.alibaba.qlexpress4.Express4Runner;
+import com.alibaba.qlexpress4.InitOptions;
+
+/**
+ * @Author TaoKan
+ * @Date 2024/9/22 12:20 PM
+ */
+public class QLExpressRunner {
+ private volatile static QLExpressRunner instance = null;
+ private Express4Runner expressRunner;
+
+ private QLExpressRunner(InitOptions initOptions){
+ expressRunner = new Express4Runner(initOptions);
+ }
+
+ //对外提供静态方法获取对象
+ public static Express4Runner getInstance(InitOptions initOptions){
+ //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
+ if(instance == null){
+ synchronized (QLExpressRunner.class){
+ //抢到锁之后再次进行判断是否为null
+ if(instance == null){
+ instance = new QLExpressRunner(initOptions);
+ }
+ }
+ }
+ return instance.expressRunner;
+ }
+
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/express/QLGlobalContext.java b/core/src/main/java/com/taobao/arthas/core/command/express/QLGlobalContext.java
new file mode 100644
index 00000000000..fa6e46f9054
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/command/express/QLGlobalContext.java
@@ -0,0 +1,70 @@
+package com.taobao.arthas.core.command.express;
+
+import com.alibaba.arthas.deps.org.slf4j.Logger;
+import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.qlexpress4.exception.PureErrReporter;
+import com.alibaba.qlexpress4.runtime.ReflectLoader;
+import com.alibaba.qlexpress4.runtime.Value;
+import com.alibaba.qlexpress4.runtime.context.ExpressContext;
+import com.alibaba.qlexpress4.runtime.data.MapItemValue;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Author TaoKan
+ * @Date 2024/9/22 12:39 PM
+ */
+public class QLGlobalContext implements ExpressContext {
+ private static final Logger logger = LoggerFactory.getLogger(QLGlobalContext.class);
+
+ private Map context;
+ private Object object;
+ private ReflectLoader reflectLoader;
+
+ public QLGlobalContext(ReflectLoader reflectLoader) {
+ this.context = new ConcurrentHashMap<>();
+ this.reflectLoader = reflectLoader;
+ }
+
+ public void put(String name, Object value){
+ context.put(name, value);
+ }
+
+ public void clear() {
+ context.clear();
+ this.context.put("reflectLoader",reflectLoader);
+ }
+
+ public void bindObj(Object object) {
+ this.object = object;
+ context.put("object",object);
+ }
+ @Override
+ public Value get(Map attachments, String variableName) {
+ if ((this.reflectLoader != null) && (this.object != null) && !variableName.startsWith("#")) {
+ return this.reflectLoader.loadField(this.object, variableName, true, PureErrReporter.INSTANCE);
+ }
+ String newVariableName = variableName.replace("#","");
+ return new MapItemValue(this.context, newVariableName);
+ }
+
+
+ public Map getContext() {
+ return context;
+ }
+
+ public void setContext(Map context) {
+ this.context = context;
+ }
+
+ public Object getObject() {
+ return object;
+ }
+
+ public void setObject(Object object) {
+ this.object = object;
+ }
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java
index b2dd6125dcd..e2216b1b684 100644
--- a/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java
+++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/OgnlCommand.java
@@ -31,13 +31,13 @@
@Name("ognl")
@Summary("Execute ognl expression.")
@Description(Constants.EXAMPLE
- + " ognl '@java.lang.System@out.println(\"hello \\u4e2d\\u6587\")' \n"
- + " ognl -x 2 '@Singleton@getInstance()' \n"
- + " ognl '@Demo@staticFiled' \n"
- + " ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'\n"
- + " ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump' \n"
- + Constants.WIKI + Constants.WIKI_HOME + "ognl\n"
- + " https://commons.apache.org/proper/commons-ognl/language-guide.html")
+ + " ognl '@java.lang.System@out.println(\"hello \\u4e2d\\u6587\")' \n"
+ + " ognl -x 2 '@Singleton@getInstance()' \n"
+ + " ognl '@Demo@staticFiled' \n"
+ + " ognl '#value1=@System@getProperty(\"java.home\"), #value2=@System@getProperty(\"java.runtime.name\"), {#value1, #value2}'\n"
+ + " ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump' \n"
+ + Constants.WIKI + Constants.WIKI_HOME + "ognl\n"
+ + " https://commons.apache.org/proper/commons-ognl/language-guide.html")
public class OgnlCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(OgnlCommand.class);
@@ -100,7 +100,7 @@ public void process(CommandProcess process) {
classLoader = ClassLoader.getSystemClassLoader();
}
- Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader);
+ Express unpooledExpress = ExpressFactory.unpooledExpressByOGNL(classLoader);
try {
// https://github.com/alibaba/arthas/issues/2892
Object value = unpooledExpress.bind(new Object()).get(express);
diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ExpressTypeEnum.java b/core/src/main/java/com/taobao/arthas/core/command/model/ExpressTypeEnum.java
new file mode 100644
index 00000000000..5191f875d04
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/command/model/ExpressTypeEnum.java
@@ -0,0 +1,21 @@
+package com.taobao.arthas.core.command.model;
+
+/**
+ * @Author TaoKan
+ * @Date 2024/9/22 7:32 AM
+ */
+public enum ExpressTypeEnum
+{
+ OGNL("ognl"),
+ QLEXPRESS("qlexpress");
+
+ private String expressType;
+
+ ExpressTypeEnum(String expressType) {
+ this.expressType = expressType;
+ }
+
+ public String getExpressType() {
+ return expressType;
+ }
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/QLExpressConfigModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/QLExpressConfigModel.java
new file mode 100644
index 00000000000..05348c6213d
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/command/model/QLExpressConfigModel.java
@@ -0,0 +1,97 @@
+package com.taobao.arthas.core.command.model;
+
+/**
+ * @Author TaoKan
+ * @Date 2024/9/22 7:51 AM
+ */
+public class QLExpressConfigModel {
+ //QL_OPTIONS
+ private boolean precise = false;
+ private boolean polluteUserContext = false;
+ private long timeoutMillis = -1L;
+
+ private boolean cache = false;
+ private boolean avoidNullPointer = false;
+ private int maxArrLength = -1;
+
+
+ //INIT_OPTIONS
+ private boolean allowPrivateAccess = true;
+ private boolean debug;
+ private boolean useCacheClear;
+
+ public boolean isAllowPrivateAccess() {
+ return allowPrivateAccess;
+ }
+
+ public void setAllowPrivateAccess(boolean allowPrivateAccess) {
+ this.allowPrivateAccess = allowPrivateAccess;
+ }
+
+ public boolean isDebug() {
+ return debug;
+ }
+
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ public boolean isUseCacheClear() {
+ return useCacheClear;
+ }
+
+ public void setUseCacheClear(boolean useCacheClear) {
+ this.useCacheClear = useCacheClear;
+ }
+
+
+
+ public boolean isPrecise() {
+ return precise;
+ }
+
+ public void setPrecise(boolean precise) {
+ this.precise = precise;
+ }
+
+ public boolean isPolluteUserContext() {
+ return polluteUserContext;
+ }
+
+ public void setPolluteUserContext(boolean polluteUserContext) {
+ this.polluteUserContext = polluteUserContext;
+ }
+
+ public long getTimeoutMillis() {
+ return timeoutMillis;
+ }
+
+ public void setTimeoutMillis(long timeoutMillis) {
+ this.timeoutMillis = timeoutMillis;
+ }
+
+ public boolean isAvoidNullPointer() {
+ return avoidNullPointer;
+ }
+
+ public void setAvoidNullPointer(boolean avoidNullPointer) {
+ this.avoidNullPointer = avoidNullPointer;
+ }
+
+ public int getMaxArrLength() {
+ return maxArrLength;
+ }
+
+ public void setMaxArrLength(int maxArrLength) {
+ this.maxArrLength = maxArrLength;
+ }
+
+
+ public boolean isCache() {
+ return cache;
+ }
+
+ public void setCache(boolean cache) {
+ this.cache = cache;
+ }
+}
diff --git a/core/src/test/java/com/taobao/arthas/core/command/express/QLExpressTest.java b/core/src/test/java/com/taobao/arthas/core/command/express/QLExpressTest.java
new file mode 100644
index 00000000000..b701c276b2a
--- /dev/null
+++ b/core/src/test/java/com/taobao/arthas/core/command/express/QLExpressTest.java
@@ -0,0 +1,50 @@
+package com.taobao.arthas.core.command.express;
+
+import com.taobao.arthas.core.GlobalOptions;
+import com.taobao.arthas.core.advisor.Advice;
+import com.taobao.arthas.core.command.model.ExpressTypeEnum;
+import ognl.OgnlException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @Author TaoKan
+ * @Date 2025/1/12 5:31 PM
+ */
+public class QLExpressTest {
+
+ private Express express;
+
+ @BeforeEach
+ public void setUp() throws ExpressException {
+ FlowContext context = new FlowContext();
+ Object[] params = new Object[4];
+ params[0] = context;
+ Advice advice = Advice.newForAfterReturning(null, getClass(), null, null, params, null);
+ GlobalOptions.ExpressType = ExpressTypeEnum.QLEXPRESS.getExpressType();
+ express = ExpressFactory.unpooledExpress(null).bind(advice).bind("cost", 123);
+ }
+
+ @Test
+ public void testStringEquals() throws OgnlException, ExpressException {
+ String conditionExpress = "\"aaa\".equals(params[0].flowAttribute.getBxApp())";
+ boolean result = express.is(conditionExpress);
+ assertTrue(result);
+ }
+
+ @Test
+ public void testObjectEquals() throws OgnlException, ExpressException {
+ String conditionExpress = "params[0].flowAttribute.getBxApp().equals(\"aaa\")";
+ boolean result = express.is(conditionExpress);
+ assertTrue(result);
+ }
+
+ @Test
+ public void testEqualSign() throws OgnlException, ExpressException {
+ String conditionExpress = "\"aaa\" == params[0].flowAttribute.getBxApp()";
+ boolean result = express.is(conditionExpress);
+ assertTrue(result);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 3fabb644ba2..bba957fec9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -163,6 +163,11 @@
ognl
3.3.5
+
+ com.alibaba
+ QLExpress4
+ 4.0.0-beta.2
+
org.junit
junit-bom