Skip to content

Commit

Permalink
Merge pull request #235 from alibaba/branch_version_3.3.1.dev
Browse files Browse the repository at this point in the history
3.3.1 release
  • Loading branch information
DQinYuan authored Feb 7, 2023
2 parents e9469df + 856c032 commit 63c139f
Show file tree
Hide file tree
Showing 44 changed files with 184 additions and 442 deletions.
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ QLExpress脚本引擎被广泛应用在阿里的电商业务场景,具有以
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
</dependency>
```

Expand Down Expand Up @@ -371,7 +371,7 @@ private boolean isTrace = false;
* @return
* @throws Exception
*/
Object execute(String expressString, IExpressContext<String, Object> context, List<String> errorList, boolean isCache, boolean isTrace, Log aLog);
Object execute(String expressString, IExpressContext<String, Object> context, List<String> errorList, boolean isCache, boolean isTrace);
```

## 3、功能扩展API列表
Expand Down Expand Up @@ -809,11 +809,31 @@ assertEquals("t", expressRunner.execute("test.a", context,
null, false, true));
```

在沙箱模式下,为了进一步保障内存的安全,建议同时限制脚本能够申请的最大数组长度以及超时时间,设置方法如下:

`com.ql.util.express.test.ArrayLenCheckTest`

```java
// 限制最大申请数组长度为10, 默认没有限制
QLExpressRunStrategy.setMaxArrLength(10);
ExpressRunner runner = new ExpressRunner();
String code = "byte[] a = new byte[11];";
try {
// 20ms 超时时间
runner.execute(code, new DefaultContext<>(), null, false, false, 20);
Assert.fail();
} catch (QLException e) {
}

QLExpressRunStrategy.setMaxArrLength(-1);
// 20ms 超时时间
runner.execute(code, new DefaultContext<>(), null, false, false, 20);
```

附录:
[版本更新列表](VERSIONS.md)

## links for us
- Gitter channel - Online chat room with QLExpress developers. [Gitter channel ](https://gitter.im/QLExpress/Lobby)
- email:tianqiao@alibaba-inc.com,[email protected]
- wechart:371754252
- QLExpress blogs: https://yq.aliyun.com/album/130
- wechart:371754252
11 changes: 10 additions & 1 deletion VERSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,13 @@ public Object execute(InstructionSet[] instructionSets,IExpressContext<String,Ob

(1)高精度计算下的溢出问题修复 com.ql.util.express.test.NumberOperatorCalculatorTest

(2)多级别安全控制与沙箱模式 com.ql.util.express.example.MultiLevelSecurityTest
(2)多级别安全控制与沙箱模式 com.ql.util.express.example.MultiLevelSecurityTest

## 3.3.1版本[2023-02-03]

(1) #188 break/continue 问题修复
(2)内置脚本 cache 改成 concurrentHashMap
(3)去除 log4j 和 apache common log 的依赖, 原先部分 `execute` 参数中含有 common log ,所以部分 `execute` 签名有不兼容变更。可能会导致升级后编译不通过
(4)#233 相关安全增强
a. 扩充默认黑名单
b. 支持通过 com.ql.util.express.config.QLExpressRunStrategy#setMaxArrLength 限制脚本一次最多申请数组的大小
13 changes: 1 addition & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<packaging>jar</packaging>
<version>3.3.1-SNAPSHOT</version>
<version>3.3.1</version>
<name>QLExpress</name>
<description>QLExpress is a powerful, lightweight, dynamic language for the Java platform aimed at improving developers’ productivity in different business scenes.
</description>
Expand Down Expand Up @@ -66,17 +66,6 @@
<artifactId>commons-beanutils</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import java.util.List;

import org.apache.commons.logging.Log;

/**
* 远程缓存对象
*
Expand All @@ -25,14 +23,14 @@ public void loadCache(String expressName, String text) {
}

public Object execute(String name, IExpressContext<String, Object> context, List<String> errorList, boolean isTrace,
boolean isCatchException, Log log) {
boolean isCatchException) {
try {
CacheObject cache = (CacheObject)this.getCache(name);
if (cache == null) {
throw new RuntimeException("未获取到缓存对象.");
}
ExpressRunner expressRunner = getExpressRunner();
return expressRunner.execute(cache.getInstructionSet(), context, errorList, isTrace, isCatchException, log);
return expressRunner.execute(cache.getInstructionSet(), context, errorList, isTrace, isCatchException);
} catch (Exception e) {
throw new RuntimeException("获取缓存信息,并且执行指令集出现错误.", e);
}
Expand Down Expand Up @@ -61,5 +59,3 @@ public Object execute(String name, IExpressContext<String, Object> context, List
*/
public abstract void putCache(String key, Object object);
}


101 changes: 31 additions & 70 deletions src/main/java/com/ql/util/express/ExpressRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,13 @@
import com.ql.util.express.parse.NodeType;
import com.ql.util.express.parse.NodeTypeManager;
import com.ql.util.express.parse.Word;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* 语法分析和计算的入口类
*
* @author xuannan
*/
public class ExpressRunner {
private static final Log log = LogFactory.getLog(ExpressRunner.class);

private static final String GLOBAL_DEFINE_NAME = "全局定义";

Expand Down Expand Up @@ -127,17 +124,15 @@ public ExpressRunner(boolean isPrecise, boolean isTrace) {
}

/**
*
* @param isPrecise
* @param isTrace
* @param cacheMap user can define safe and efficient cache or use default concurrentMap
* @param cacheMap user can define safe and efficient cache or use default concurrentMap
*/
public ExpressRunner(boolean isPrecise, boolean isTrace,
Map<String, InstructionSet> cacheMap) {
this(isPrecise, isTrace, new DefaultExpressResourceLoader(), null, cacheMap);
}


public ExpressRunner(boolean isPrecise, boolean isStrace, NodeTypeManager nodeTypeManager) {
this(isPrecise, isStrace, new DefaultExpressResourceLoader(), nodeTypeManager);
}
Expand All @@ -157,7 +152,7 @@ public ExpressRunner(boolean isPrecise, boolean isTrace, IExpressResourceLoader
* @param isPrecise 是否需要高精度计算支持
* @param isTrace 是否跟踪执行指令的过程
* @param iExpressResourceLoader 表达式的资源装载器
* @param cacheMap 指令集缓存
* @param cacheMap 指令集缓存, 必须是线程安全的集合
*/
public ExpressRunner(boolean isPrecise, boolean isTrace, IExpressResourceLoader iExpressResourceLoader,
NodeTypeManager nodeTypeManager, Map<String, InstructionSet> cacheMap) {
Expand Down Expand Up @@ -225,7 +220,6 @@ public IExpressResourceLoader getExpressResourceLoader() {
return this.expressResourceLoader;
}


/**
* 添加宏定义
* 例如: macro 宏名称 { abc(userInfo.userId);}
Expand Down Expand Up @@ -537,7 +531,8 @@ public void addOperatorWithAlias(String keyWordName, String realKeyWordName, Str
}
boolean isExist = this.operatorManager.isExistOperator(realNodeType.getName());
if (!isExist && errorInfo != null) {
throw new QLException("关键字:" + realKeyWordName + "是通过指令来实现的,不能设置错误的提示信息,errorInfo 必须是 null");
throw new QLException(
"关键字:" + realKeyWordName + "是通过指令来实现的,不能设置错误的提示信息,errorInfo 必须是 null");
}
if (!isExist || errorInfo == null) {
//不需要新增操作符号,只需要建立一个关键子即可
Expand Down Expand Up @@ -576,36 +571,14 @@ public void clearExpressCache() {
* @param errorList
* @param isTrace
* @param isCatchException
* @param log
* @return
* @throws Exception
*/
public Object executeByExpressName(String name, IExpressContext<String, Object> context, List<String> errorList,
boolean isTrace, boolean isCatchException, Log log) throws Exception {
boolean isTrace, boolean isCatchException) throws Exception {
return InstructionSetRunner.executeOuter(this, this.loader.getInstructionSet(name), this.loader, context,
errorList, isTrace, isCatchException, log, false);
}

///**
// * 执行指令集(兼容老接口,请不要自己管理指令缓存,直接使用execute(InstructionSet instructionSet,....... )
// * 清理缓存可以使用clearExpressCache()函数
// *
// * @param instructionSets
// * @param context
// * @param errorList
// * @param isTrace
// * @param isCatchException
// * @param log
// * @return
// * @throws Exception
// * @deprecated
// */
//@Deprecated
//public Object execute(InstructionSet[] instructionSets, IExpressContext<String, Object> context,
// List<String> errorList, boolean isTrace, boolean isCatchException, Log log) throws Exception {
// return InstructionSetRunner.executeOuter(this, instructionSets[0], this.loader, context, errorList,
// isTrace, isCatchException, log, false);
//}
errorList, isTrace, isCatchException, false);
}

/**
* 执行指令集
Expand All @@ -615,14 +588,12 @@ public Object executeByExpressName(String name, IExpressContext<String, Object>
* @param errorList
* @param isTrace
* @param isCatchException
* @param log
* @return
* @throws Exception
*/
public Object execute(InstructionSet instructionSet, IExpressContext<String, Object> context,
List<String> errorList, boolean isTrace, boolean isCatchException, Log log) throws Exception {
return InstructionSetRunner.executeOuter(this, instructionSet, this.loader, context, errorList,
isTrace, isCatchException, log, false);
List<String> errorList, boolean isTrace, boolean isCatchException) throws Exception {
return executeReentrant(instructionSet, context, errorList, isTrace, isCatchException);
}

/**
Expand All @@ -642,7 +613,7 @@ public Object execute(String expressString, IExpressContext<String, Object> cont
//设置超时毫秒时间
QLExpressTimer.setTimer(timeoutMillis);
try {
return this.execute(expressString, context, errorList, isCache, isTrace, null);
return this.execute(expressString, context, errorList, isCache, isTrace);
} finally {
QLExpressTimer.reset();
}
Expand All @@ -661,48 +632,38 @@ public Object execute(String expressString, IExpressContext<String, Object> cont
*/
public Object execute(String expressString, IExpressContext<String, Object> context, List<String> errorList,
boolean isCache, boolean isTrace) throws Exception {
return this.execute(expressString, context, errorList, isCache, isTrace, null);
}

/**
* 执行一段文本
*
* @param expressString 程序文本
* @param context 执行上下文
* @param errorList 输出的错误信息List
* @param isCache 是否使用Cache中的指令集
* @param isTrace 是否输出详细的执行指令信息
* @param log 输出的log
* @return
* @throws Exception
*/
public Object execute(String expressString, IExpressContext<String, Object> context, List<String> errorList,
boolean isCache, boolean isTrace, Log log) throws Exception {
InstructionSet parseResult;
if (isCache) {
parseResult = expressInstructionSetCache.get(expressString);
if (parseResult == null) {
expressInstructionSetCache.putIfAbsent(expressString,
parseResult = this.parseInstructionSet(expressString));
synchronized (expressInstructionSetCache) {
// 防止在第一次执行时多次计算 parseInstructionSet, 所以需要加锁
// 可以优化成分段锁, 而不是锁整个 cache, 进一步优化可以使用定制的 concurrentMap
parseResult = expressInstructionSetCache.get(expressString);
if (parseResult == null) {
expressInstructionSetCache.put(expressString,
parseResult = this.parseInstructionSet(expressString));
}
}
}
} else {
parseResult = this.parseInstructionSet(expressString);
}
return executeReentrant(parseResult, context, errorList, isTrace, log);
return executeReentrant(parseResult, context, errorList, isTrace, false);
}

private Object executeReentrant(InstructionSet sets, IExpressContext<String, Object> iExpressContext,
List<String> errorList, boolean isTrace, Log log) throws Exception {
List<String> errorList, boolean isTrace, boolean isCatchException) throws Exception {
try {
int reentrantCount = threadReentrantCount.get() + 1;
threadReentrantCount.set(reentrantCount);

return reentrantCount > 1 ?
// 线程重入
InstructionSetRunner.execute(this, sets, this.loader, iExpressContext, errorList, isTrace, false, true,
log, false) :
InstructionSetRunner.executeOuter(this, sets, this.loader, iExpressContext, errorList, isTrace, false,
log, false);
InstructionSetRunner.execute(this, sets, this.loader, iExpressContext, errorList, isTrace,
isCatchException, true, false) :
InstructionSetRunner.executeOuter(this, sets, this.loader, iExpressContext, errorList, isTrace,
isCatchException, false);
} finally {
threadReentrantCount.set(threadReentrantCount.get() - 1);
}
Expand All @@ -726,8 +687,8 @@ public InstructionSet parseInstructionSet(String text) throws Exception {

ExpressNode root = this.parse.parse(this.rootExpressPackage, text, isTrace, selfDefineClass);
InstructionSet result = createInstructionSet(root, "main");
if (this.isTrace && log.isDebugEnabled()) {
log.debug(result);
if (this.isTrace) {
System.out.println(result);
}
return result;
} catch (QLCompileException e) {
Expand Down Expand Up @@ -756,7 +717,8 @@ public ExportItem[] getExportInfo() {
public InstructionSet getInstructionSetFromLocalCache(String expressString) throws Exception {
InstructionSet parseResult = expressInstructionSetCache.get(expressString);
if (parseResult == null) {
expressInstructionSetCache.putIfAbsent(expressString, parseResult = this.parseInstructionSet(expressString));
expressInstructionSetCache.putIfAbsent(expressString,
parseResult = this.parseInstructionSet(expressString));
}
return parseResult;
}
Expand Down Expand Up @@ -846,15 +808,14 @@ public boolean checkSyntax(String text, boolean mockRemoteJavaClass, List<String
ExpressNode root = this.parse.parse(this.rootExpressPackage, words, text, isTrace, selfDefineClass,
mockRemoteJavaClass);
InstructionSet result = createInstructionSet(root, "main");
if (this.isTrace && log.isDebugEnabled()) {
log.debug(result);
if (this.isTrace) {
System.out.println(result);
}
if (mockRemoteJavaClass && remoteJavaClassNames != null) {
remoteJavaClassNames.addAll(Arrays.asList(result.getVirClasses()));
}
return true;
} catch (Exception e) {
log.error("checkSyntax has Exception", e);
return false;
}
}
Expand Down
Loading

0 comments on commit 63c139f

Please sign in to comment.