add timeout and memory limit

This commit is contained in:
ekibun
2022-05-20 00:40:44 +08:00
parent 232ea07e89
commit 32aac42c19
8 changed files with 426 additions and 316 deletions

View File

@@ -8,6 +8,7 @@
## 0.3.7
* add timeout and memory limit
* fixed compiler error in windows release
* fixed crash when encoding Error object
* updated to latest quickjs

View File

@@ -160,89 +160,91 @@ LF:157
LH:148
end_of_record
SF:lib\src\engine.dart
DA:33,3
DA:39,3
DA:40,3
DA:41,6
DA:44,3
DA:45,3
DA:46,9
DA:47,3
DA:48,6
DA:48,3
DA:49,6
DA:51,3
DA:52,15
DA:56,3
DA:58,3
DA:60,3
DA:62,3
DA:64,6
DA:52,3
DA:53,3
DA:54,9
DA:55,3
DA:56,6
DA:57,6
DA:59,3
DA:60,15
DA:64,3
DA:66,3
DA:67,3
DA:68,6
DA:69,6
DA:68,3
DA:70,3
DA:71,6
DA:72,3
DA:72,6
DA:74,3
DA:75,3
DA:76,3
DA:77,3
DA:78,6
DA:80,0
DA:76,6
DA:77,6
DA:78,3
DA:79,6
DA:80,3
DA:82,3
DA:83,3
DA:84,3
DA:85,9
DA:86,3
DA:85,3
DA:86,6
DA:88,0
DA:90,0
DA:91,0
DA:92,0
DA:94,0
DA:95,0
DA:90,3
DA:91,3
DA:92,3
DA:93,9
DA:94,3
DA:96,0
DA:98,0
DA:99,0
DA:100,0
DA:101,0
DA:102,0
DA:103,0
DA:107,3
DA:108,3
DA:109,3
DA:110,3
DA:111,6
DA:115,3
DA:104,0
DA:106,0
DA:107,0
DA:108,0
DA:109,0
DA:110,0
DA:111,0
DA:115,6
DA:116,3
DA:117,3
DA:118,3
DA:119,3
DA:120,6
DA:122,3
DA:124,3
DA:119,9
DA:120,3
DA:121,6
DA:125,3
DA:126,3
DA:130,3
DA:131,3
DA:127,3
DA:128,3
DA:129,3
DA:130,6
DA:132,3
DA:135,6
DA:134,3
DA:135,3
DA:136,3
DA:137,3
DA:144,3
DA:145,9
DA:140,3
DA:141,3
DA:142,3
DA:145,6
DA:146,3
DA:151,3
DA:147,3
DA:154,3
DA:155,9
DA:156,3
DA:157,3
DA:158,3
DA:164,9
DA:165,3
DA:161,3
DA:166,3
DA:167,3
DA:168,3
DA:169,3
LF:81
LH:67
DA:174,9
DA:175,3
DA:176,3
DA:178,3
DA:179,3
LF:83
LH:69
end_of_record
SF:lib\src\isolate.dart
DA:11,9
@@ -310,87 +312,91 @@ DA:109,6
DA:110,3
DA:111,3
DA:112,3
DA:113,6
DA:115,3
DA:118,3
DA:120,9
DA:121,6
DA:124,3
DA:126,18
DA:127,3
DA:128,3
DA:129,9
DA:113,3
DA:114,3
DA:115,6
DA:117,3
DA:120,3
DA:122,9
DA:123,6
DA:126,3
DA:128,18
DA:129,3
DA:130,3
DA:131,3
DA:135,6
DA:137,3
DA:131,9
DA:132,3
DA:133,3
DA:137,6
DA:139,3
DA:140,3
DA:141,6
DA:141,3
DA:142,3
DA:143,3
DA:143,6
DA:144,3
DA:147,3
DA:149,6
DA:150,3
DA:151,3
DA:155,6
DA:158,0
DA:159,0
DA:163,6
DA:184,3
DA:190,3
DA:191,3
DA:145,3
DA:146,3
DA:149,3
DA:151,6
DA:152,3
DA:153,3
DA:157,6
DA:160,0
DA:161,0
DA:165,6
DA:192,3
DA:193,3
DA:195,3
DA:196,3
DA:197,3
DA:200,3
DA:201,3
DA:202,6
DA:203,6
DA:204,3
DA:202,3
DA:203,3
DA:205,3
DA:206,3
DA:207,3
DA:208,3
DA:210,6
DA:211,3
DA:212,6
DA:214,0
DA:217,0
DA:209,3
DA:213,3
DA:214,6
DA:215,6
DA:216,3
DA:219,3
DA:220,3
DA:221,6
DA:223,18
DA:225,0
DA:222,6
DA:223,3
DA:224,6
DA:226,0
DA:229,0
DA:230,0
DA:231,0
DA:232,0
DA:234,6
DA:238,3
DA:239,3
DA:240,3
DA:242,6
DA:243,3
DA:244,6
DA:246,3
DA:248,6
DA:249,3
DA:232,3
DA:233,6
DA:235,18
DA:237,0
DA:241,0
DA:242,0
DA:243,0
DA:244,0
DA:246,6
DA:250,3
DA:251,0
DA:251,3
DA:252,3
DA:254,6
DA:255,3
DA:256,6
DA:258,3
DA:263,3
DA:260,6
DA:261,3
DA:262,3
DA:263,0
DA:264,3
DA:265,6
DA:266,6
DA:271,3
DA:273,6
DA:274,3
DA:275,6
DA:276,0
DA:277,3
LF:144
LH:132
DA:270,3
DA:275,3
DA:276,3
DA:277,6
DA:278,6
DA:283,3
DA:285,6
DA:286,3
DA:287,6
DA:288,0
DA:289,3
LF:148
LH:136
end_of_record
SF:lib\src\object.dart
DA:14,3
@@ -582,182 +588,185 @@ DA:142,0
DA:143,0
DA:147,9
DA:148,3
DA:159,9
DA:160,3
DA:172,3
DA:174,6
DA:176,9
DA:160,9
DA:161,3
DA:174,3
DA:176,6
DA:178,9
DA:180,3
DA:181,6
DA:185,9
DA:187,3
DA:192,3
DA:193,3
DA:194,6
DA:195,12
DA:198,3
DA:202,6
DA:203,9
DA:211,0
DA:212,0
DA:223,9
DA:224,3
DA:231,3
DA:234,3
DA:235,6
DA:238,12
DA:180,9
DA:182,3
DA:183,6
DA:187,9
DA:189,3
DA:194,3
DA:195,3
DA:196,6
DA:197,12
DA:200,3
DA:205,6
DA:206,9
DA:214,0
DA:215,0
DA:227,9
DA:228,3
DA:239,9
DA:240,3
DA:241,12
DA:243,6
DA:244,6
DA:245,6
DA:246,12
DA:247,3
DA:248,18
DA:249,3
DA:252,6
DA:253,6
DA:254,3
DA:255,3
DA:263,9
DA:264,3
DA:275,9
DA:276,3
DA:283,3
DA:284,6
DA:285,6
DA:286,0
DA:287,6
DA:294,9
DA:295,3
DA:305,9
DA:306,3
DA:320,9
DA:321,3
DA:332,3
DA:250,3
DA:251,6
DA:254,12
DA:256,3
DA:257,12
DA:259,6
DA:260,6
DA:261,6
DA:262,12
DA:263,3
DA:264,18
DA:265,3
DA:268,6
DA:269,6
DA:270,3
DA:271,3
DA:279,9
DA:280,3
DA:291,9
DA:292,3
DA:299,3
DA:300,6
DA:301,6
DA:302,6
DA:303,0
DA:304,6
DA:311,9
DA:312,3
DA:322,9
DA:323,3
DA:337,9
DA:338,3
DA:339,3
DA:340,6
DA:343,3
DA:347,3
DA:348,3
DA:349,21
DA:356,9
DA:357,3
DA:367,9
DA:368,3
DA:378,9
DA:379,3
DA:390,9
DA:391,3
DA:403,9
DA:404,3
DA:416,9
DA:417,3
DA:429,9
DA:430,3
DA:438,3
DA:442,3
DA:443,6
DA:444,3
DA:453,0
DA:454,0
DA:466,9
DA:467,3
DA:477,9
DA:478,3
DA:490,9
DA:491,3
DA:500,3
DA:505,6
DA:513,0
DA:514,0
DA:523,0
DA:528,0
DA:535,9
DA:536,3
DA:548,0
DA:549,0
DA:561,9
DA:562,3
DA:574,9
DA:575,3
DA:587,9
DA:588,3
DA:600,9
DA:601,3
DA:613,9
DA:614,3
DA:622,3
DA:626,6
DA:627,6
DA:628,3
DA:629,6
DA:637,9
DA:638,3
DA:646,3
DA:650,3
DA:651,6
DA:349,3
DA:355,3
DA:356,3
DA:357,6
DA:360,3
DA:364,3
DA:365,3
DA:366,21
DA:373,9
DA:374,3
DA:384,9
DA:385,3
DA:395,9
DA:396,3
DA:407,9
DA:408,3
DA:420,9
DA:421,3
DA:433,9
DA:434,3
DA:446,9
DA:447,3
DA:455,3
DA:459,3
DA:460,6
DA:461,3
DA:470,0
DA:471,0
DA:483,9
DA:484,3
DA:494,9
DA:495,3
DA:507,9
DA:508,3
DA:517,3
DA:522,6
DA:530,0
DA:531,0
DA:540,0
DA:545,0
DA:552,9
DA:553,3
DA:565,0
DA:566,0
DA:578,9
DA:579,3
DA:591,9
DA:592,3
DA:604,9
DA:605,3
DA:617,9
DA:618,3
DA:630,9
DA:631,3
DA:639,3
DA:643,6
DA:644,6
DA:645,3
DA:646,6
DA:654,9
DA:655,3
DA:664,9
DA:665,3
DA:678,9
DA:679,3
DA:692,9
DA:693,3
DA:706,9
DA:707,3
DA:719,9
DA:720,3
DA:732,9
DA:733,3
DA:745,9
DA:746,3
DA:757,9
DA:758,3
DA:771,9
DA:772,3
DA:789,9
DA:790,3
DA:805,9
DA:806,3
DA:818,9
DA:819,3
DA:831,9
DA:832,3
DA:663,3
DA:667,3
DA:668,6
DA:672,3
DA:681,9
DA:682,3
DA:695,9
DA:696,3
DA:709,9
DA:710,3
DA:723,9
DA:724,3
DA:736,9
DA:737,3
DA:749,9
DA:750,3
DA:762,9
DA:763,3
DA:774,9
DA:775,3
DA:788,9
DA:789,3
DA:806,9
DA:807,3
DA:822,9
DA:823,3
DA:835,9
DA:836,3
DA:848,9
DA:849,3
DA:864,9
DA:865,3
DA:874,9
DA:875,3
DA:878,12
DA:885,9
DA:886,3
DA:903,9
DA:904,3
DA:915,3
DA:922,15
DA:923,3
DA:924,9
DA:925,3
DA:926,6
DA:928,6
DA:930,9
DA:931,3
DA:865,9
DA:866,3
DA:881,9
DA:882,3
DA:891,9
DA:892,3
DA:895,12
DA:902,9
DA:903,3
DA:920,9
DA:921,3
DA:932,3
DA:933,21
DA:940,9
DA:941,3
DA:951,9
DA:952,3
DA:962,9
DA:963,3
DA:974,9
DA:975,3
DA:987,9
DA:988,3
LF:215
LH:192
DA:939,15
DA:940,3
DA:941,9
DA:942,3
DA:943,6
DA:945,6
DA:947,9
DA:948,3
DA:949,3
DA:950,21
DA:957,9
DA:958,3
DA:968,9
DA:969,3
DA:979,9
DA:980,3
DA:991,9
DA:992,3
DA:1004,9
DA:1005,3
LF:218
LH:195
end_of_record

View File

@@ -1,5 +1,5 @@
/*
* @Description:
* @Description:
* @Author: ekibun
* @Date: 2020-09-06 18:32:45
* @LastEditors: ekibun
@@ -33,13 +33,17 @@ extern "C"
return new JSValue(JS_NULL);
}
struct RuntimeOpaque {
JSChannel * channel;
int64_t timeout;
int64_t start;
};
JSModuleDef *js_module_loader(
JSContext *ctx,
const char *module_name, void *opaque)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
const char *str = (char *)channel(ctx, JSChannelType_MODULE, (void *)module_name);
const char *str = (char *)((RuntimeOpaque *)opaque)->channel(ctx, JSChannelType_MODULE, (void *)module_name);
if (str == 0)
return NULL;
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
@@ -54,13 +58,13 @@ extern "C"
JSValue js_channel(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
RuntimeOpaque *opaque = (RuntimeOpaque *)JS_GetRuntimeOpaque(rt);
void *data[4];
data[0] = &this_val;
data[1] = &argc;
data[2] = argv;
data[3] = func_data;
return *(JSValue *)channel(ctx, JSChannelType_METHON, data);
return *(JSValue *)opaque->channel(ctx, JSChannelType_METHON, data);
}
void js_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
@@ -69,17 +73,26 @@ extern "C"
{
if (is_handled)
return;
JSRuntime *rt = JS_GetRuntime(ctx);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
channel(ctx, JSChannelType_PROMISE_TRACK, &reason);
((RuntimeOpaque *)opaque)->channel(ctx, JSChannelType_PROMISE_TRACK, &reason);
}
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
int js_interrupt_handler(JSRuntime * rt, void * opaque) {
RuntimeOpaque *op = (RuntimeOpaque *)opaque;
if(op->timeout && op->start && (clock() - op->start) > op->timeout * CLOCKS_PER_SEC / 1000) {
op->start = 0;
return 1;
}
return 0;
}
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel, int64_t timeout)
{
JSRuntime *rt = JS_NewRuntime();
JS_SetRuntimeOpaque(rt, (void *)channel);
JS_SetHostPromiseRejectionTracker(rt, js_promise_rejection_tracker, nullptr);
JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr);
RuntimeOpaque *opaque = new RuntimeOpaque({channel, timeout, 0});
JS_SetRuntimeOpaque(rt, opaque);
JS_SetHostPromiseRejectionTracker(rt, js_promise_rejection_tracker, opaque);
JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, opaque);
JS_SetInterruptHandler(rt, js_interrupt_handler, opaque);
return rt;
}
@@ -93,13 +106,14 @@ extern "C"
JSClassDef def{
name,
// destructor
[](JSRuntime *rt, JSValue obj) noexcept {
[](JSRuntime *rt, JSValue obj) noexcept
{
JSClassID classid = JS_GetClassID(obj);
void *opaque = JS_GetOpaque(obj, classid);
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
if (channel == nullptr)
RuntimeOpaque *runtimeOpaque = (RuntimeOpaque *)JS_GetRuntimeOpaque(rt);
if (runtimeOpaque == nullptr)
return;
channel((JSContext *)rt, JSChannelType_FREE_OBJECT, opaque);
runtimeOpaque->channel((JSContext *)rt, JSChannelType_FREE_OBJECT, opaque);
}};
int e = JS_NewClass(rt, QJSClassId, &def);
if (e < 0)
@@ -130,8 +144,16 @@ extern "C"
JS_SetMaxStackSize(rt, stack_size);
}
DLLEXPORT void jsSetMemoryLimit(JSRuntime *rt, size_t limit)
{
JS_SetMemoryLimit(rt, limit);
}
DLLEXPORT void jsFreeRuntime(JSRuntime *rt)
{
RuntimeOpaque *opauqe = (RuntimeOpaque *)JS_GetRuntimeOpaque(rt);
if (opauqe)
delete opauqe;
JS_SetRuntimeOpaque(rt, nullptr);
JS_FreeRuntime(rt);
}
@@ -143,6 +165,7 @@ extern "C"
DLLEXPORT JSContext *jsNewContext(JSRuntime *rt)
{
JS_UpdateStackTop(rt);
JSContext *ctx = JS_NewContext(rt);
return ctx;
}
@@ -157,10 +180,16 @@ extern "C"
return JS_GetRuntime(ctx);
}
void js_begin_call(JSRuntime *rt) {
JS_UpdateStackTop(rt);
RuntimeOpaque * opaque = (RuntimeOpaque *)JS_GetRuntimeOpaque(rt);
if(opaque) opaque->start = clock();
}
DLLEXPORT JSValue *jsEval(JSContext *ctx, const char *input, size_t input_len, const char *filename, int32_t eval_flags)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JS_UpdateStackTop(rt);
js_begin_call(rt);
JSValue *ret = new JSValue(JS_Eval(ctx, input, input_len, filename, eval_flags));
return ret;
}
@@ -261,7 +290,7 @@ extern "C"
DLLEXPORT const char *jsToCString(JSContext *ctx, JSValueConst *val)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JS_UpdateStackTop(rt);
js_begin_call(rt);
const char *ret = JS_ToCString(ctx, *val);
return ret;
}
@@ -353,7 +382,7 @@ extern "C"
int32_t argc, JSValueConst *argv)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JS_UpdateStackTop(rt);
js_begin_call(rt);
JSValue *ret = new JSValue(JS_Call(ctx, *func_obj, *this_obj, argc, argv));
return ret;
}
@@ -370,7 +399,7 @@ extern "C"
DLLEXPORT int32_t jsExecutePendingJob(JSRuntime *rt)
{
JS_UpdateStackTop(rt);
js_begin_call(rt);
JSContext *ctx;
int ret = JS_ExecutePendingJob(rt, &ctx);
return ret;

View File

@@ -25,7 +25,7 @@ extern "C"
DLLEXPORT JSValue *jsNULL();
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel);
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel, int64_t timeout);
DLLEXPORT uint32_t jsNewClass(JSContext *ctx, const char *name);
@@ -35,6 +35,8 @@ extern "C"
DLLEXPORT void jsSetMaxStackSize(JSRuntime *rt, size_t stack_size);
DLLEXPORT void jsSetMemoryLimit(JSRuntime *rt, size_t limit);
DLLEXPORT void jsFreeRuntime(JSRuntime *rt);
DLLEXPORT JSValue *jsNewCFunction(JSContext *ctx, JSValue *funcData);

View File

@@ -21,6 +21,12 @@ class FlutterQjs {
/// Max stack size for quickjs.
final int? stackSize;
/// Max stack size for quickjs.
final int? timeout;
/// Max memory for quickjs.
final int? memoryLimit;
/// Message Port for event loop. Close it to stop dispatching event loop.
ReceivePort port = ReceivePort();
@@ -33,6 +39,8 @@ class FlutterQjs {
FlutterQjs({
this.moduleHandler,
this.stackSize,
this.timeout,
this.memoryLimit,
this.hostPromiseRejectionHandler,
});
@@ -104,9 +112,11 @@ class FlutterQjs {
}
return err;
}
}, port);
}, timeout ?? 0, port);
final stackSize = this.stackSize ?? 0;
if (stackSize > 0) jsSetMaxStackSize(rt, stackSize);
final memoryLimit = this.memoryLimit ?? 0;
if (memoryLimit > 0) jsSetMemoryLimit(rt, memoryLimit);
_rt = rt;
_ctx = jsNewContext(rt);
}

View File

@@ -156,11 +156,13 @@ typedef _JSChannelNative = Pointer<JSValue> Function(
/// JSRuntime *jsNewRuntime(JSChannel channel)
final Pointer<JSRuntime> Function(
Pointer<NativeFunction<_JSChannelNative>>,
int,
) _jsNewRuntime = _qjsLib
.lookup<
NativeFunction<
Pointer<JSRuntime> Function(
Pointer<NativeFunction<_JSChannelNative>>,
Int64,
)>>('jsNewRuntime')
.asFunction();
@@ -197,9 +199,10 @@ Pointer<JSValue>? channelDispacher(
Pointer<JSRuntime> jsNewRuntime(
_JSChannel callback,
int timeout,
ReceivePort port,
) {
final rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher));
final rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher), timeout);
runtimeOpaques[rt] = _RuntimeOpaque(callback, port);
return rt;
}
@@ -217,6 +220,19 @@ final void Function(
)>>('jsSetMaxStackSize')
.asFunction();
/// DLLEXPORT void jsSetMemoryLimit(JSRuntime *rt, size_t limit);
final void Function(
Pointer<JSRuntime>,
int,
) jsSetMemoryLimit = _qjsLib
.lookup<
NativeFunction<
Void Function(
Pointer<JSRuntime>,
IntPtr,
)>>('jsSetMemoryLimit')
.asFunction();
/// void jsFreeRuntime(JSRuntime *rt)
final void Function(
Pointer<JSRuntime>,
@@ -282,6 +298,7 @@ final Pointer<JSContext> Function(
Pointer<JSContext> jsNewContext(Pointer<JSRuntime> rt) {
final ctx = _jsNewContext(rt);
if (ctx.address == 0) throw Exception('Context create failed!');
final runtimeOpaque = runtimeOpaques[rt];
if (runtimeOpaque == null) throw Exception('Runtime has been released!');
runtimeOpaque._dartObjectClassId = jsNewClass(ctx, 'DartObject');

View File

@@ -109,6 +109,8 @@ void _runJsIsolate(Map spawnMessage) async {
sendPort.send(port.sendPort);
final qjs = FlutterQjs(
stackSize: spawnMessage[#stackSize],
timeout: spawnMessage[#timeout],
memoryLimit: spawnMessage[#memoryLimit],
hostPromiseRejectionHandler: (reason) {
sendPort.send({
#type: #hostPromiseRejection,
@@ -171,6 +173,12 @@ class IsolateQjs {
/// Max stack size for quickjs.
final int? stackSize;
/// Max stack size for quickjs.
final int? timeout;
/// Max memory for quickjs.
final int? memoryLimit;
/// Asynchronously handler to manage js module.
final _JsAsyncModuleHandler? moduleHandler;
@@ -184,6 +192,8 @@ class IsolateQjs {
IsolateQjs({
this.moduleHandler,
this.stackSize,
this.timeout,
this.memoryLimit,
this.hostPromiseRejectionHandler,
});
@@ -195,6 +205,8 @@ class IsolateQjs {
{
#port: port.sendPort,
#stackSize: stackSize,
#timeout: timeout,
#memoryLimit: memoryLimit,
},
errorsAreFatal: true,
);

View File

@@ -121,6 +121,36 @@ void main() async {
stderr.write(result.stderr);
expect(result.exitCode, 0);
});
test('infinite loop', () async {
final qjs = FlutterQjs(
timeout: 1000,
);
qjs.dispatch();
var result = await qjs.evaluate('1');
expect(result, 1, reason: 'eval module');
try {
await qjs.evaluate('while(true) {}');
throw 'Error not throw';
} on JSError catch (e) {
expect(e.message, startsWith('InternalError: interrupted'),
reason: 'throw interrupted');
}
await qjs.close();
});
test('memory leak', () async {
final qjs = FlutterQjs(
memoryLimit: 1000000,
);
qjs.dispatch();
try {
await qjs.evaluate('new Array(1000000).fill(0)');
throw 'Error not throw';
} on JSError catch (e) {
expect(e.message, startsWith('InternalError: out of memory'),
reason: 'throw interrupted');
}
await qjs.close();
});
test('module', () async {
final qjs = IsolateQjs(
moduleHandler: (name) async {