Skip to content
This repository was archived by the owner on Mar 29, 2024. It is now read-only.

Commit 7bc8758

Browse files
committed
Add time and memory limits support
1 parent f4bb4ef commit 7bc8758

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2325
-130
lines changed

config.m4

+1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ if test "$PHP_V8" != "no"; then
191191
src/php_v8_startup_data.cc \
192192
src/php_v8_heap_statistics.cc \
193193
src/php_v8_isolate.cc \
194+
src/php_v8_isolate_limits.cc \
194195
src/php_v8_context.cc \
195196
src/php_v8_object_template.cc \
196197
src/php_v8_function_template.cc \

src/php_v8_exceptions.cc

+44-10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
zend_class_entry* php_v8_generic_exception_class_entry;
2525
zend_class_entry* php_v8_try_catch_exception_class_entry;
2626
zend_class_entry* php_v8_termination_exception_class_entry;
27+
zend_class_entry* php_v8_abstract_resource_limit_exception_class_entry;
28+
zend_class_entry* php_v8_time_limit_exception_class_entry;
29+
zend_class_entry* php_v8_memory_limit_exception_class_entry;
2730

2831
zend_class_entry* php_v8_value_exception_class_entry;
2932
zend_class_entry* php_v8_script_exception_class_entry;
@@ -61,23 +64,34 @@ void php_v8_create_try_catch_exception(zval *return_value, php_v8_isolate_t *php
6164
zend_class_entry* ce = NULL;
6265
const char *message = NULL;
6366

64-
v8::String::Utf8Value exception(try_catch->Exception());
65-
66-
// TODO: can we remove some of this check as redundant?
67-
if (try_catch->Exception()->IsNull() && try_catch->Message().IsEmpty() && !try_catch->CanContinue() && try_catch->HasTerminated()) {
68-
ce = php_v8_termination_exception_class_entry;
69-
message = "Execution terminated";
67+
PHP_V8_DECLARE_LIMITS(php_v8_isolate);
68+
69+
if ((try_catch == NULL) || (try_catch->Exception()->IsNull() && try_catch->Message().IsEmpty() && !try_catch->CanContinue() && try_catch->HasTerminated())) {
70+
if (limits->time_limit_hit) {
71+
ce = php_v8_time_limit_exception_class_entry;
72+
message = "Time limit exceeded";
73+
} else if (limits->memory_limit_hit) {
74+
ce = php_v8_memory_limit_exception_class_entry;
75+
message = "Memory limit exceeded";
76+
} else {
77+
ce = php_v8_termination_exception_class_entry;
78+
message = "Execution terminated";
79+
}
80+
81+
object_init_ex(return_value, ce);
82+
zend_update_property_string(php_v8_try_catch_exception_class_entry, return_value, ZEND_STRL("message"), message);
7083
} else {
84+
v8::String::Utf8Value exception(try_catch->Exception());
85+
7186
ce = php_v8_try_catch_exception_class_entry;
7287
PHP_V8_CONVERT_UTF8VALUE_TO_STRING_WITH_CHECK_NODECL(exception, message);
73-
}
7488

75-
// TODO: separate ce for Error and it children classes. Or should it be done in userland?
89+
object_init_ex(return_value, ce);
90+
zend_update_property_string(php_v8_try_catch_exception_class_entry, return_value, ZEND_STRL("message"), message);
91+
}
7692

77-
object_init_ex(return_value, ce);
7893
PHP_V8_TRY_CATCH_EXCEPTION_STORE_ISOLATE(return_value, &php_v8_isolate->this_ptr);
7994
PHP_V8_TRY_CATCH_EXCEPTION_STORE_CONTEXT(return_value, &php_v8_context->this_ptr);
80-
zend_update_property_string(php_v8_try_catch_exception_class_entry, return_value, ZEND_STRL("message"), message);
8195

8296
php_v8_try_catch_create_from_try_catch(&try_catch_zv, php_v8_isolate, php_v8_context, try_catch);
8397
PHP_V8_TRY_CATCH_EXCEPTION_STORE_TRY_CATCH(return_value, &try_catch_zv);
@@ -186,6 +200,17 @@ static const zend_function_entry php_v8_termination_exception_methods[] = {
186200
PHP_FE_END
187201
};
188202

203+
static const zend_function_entry php_v8_abstract_resource_limit_exception_methods[] = {
204+
PHP_FE_END
205+
};
206+
207+
static const zend_function_entry php_v8_time_limit_exception_methods[] = {
208+
PHP_FE_END
209+
};
210+
211+
static const zend_function_entry php_v8_memory_limit_exception_methods[] = {
212+
PHP_FE_END
213+
};
189214

190215
static const zend_function_entry php_v8_script_exception_methods[] = {
191216
PHP_FE_END
@@ -214,6 +239,15 @@ PHP_MINIT_FUNCTION(php_v8_exceptions) {
214239
INIT_NS_CLASS_ENTRY(ce, PHP_V8_NS "\\Exceptions", "TerminationException", php_v8_termination_exception_methods);
215240
php_v8_termination_exception_class_entry = zend_register_internal_class_ex(&ce, php_v8_try_catch_exception_class_entry);
216241

242+
INIT_NS_CLASS_ENTRY(ce, PHP_V8_NS "\\Exceptions", "AbstractResourceLimitException", php_v8_abstract_resource_limit_exception_methods);
243+
php_v8_abstract_resource_limit_exception_class_entry = zend_register_internal_class_ex(&ce, php_v8_termination_exception_class_entry);
244+
php_v8_abstract_resource_limit_exception_class_entry->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
245+
246+
INIT_NS_CLASS_ENTRY(ce, PHP_V8_NS "\\Exceptions", "TimeLimitException", php_v8_time_limit_exception_methods);
247+
php_v8_time_limit_exception_class_entry = zend_register_internal_class_ex(&ce, php_v8_abstract_resource_limit_exception_class_entry);
248+
249+
INIT_NS_CLASS_ENTRY(ce, PHP_V8_NS "\\Exceptions", "MemoryLimitException", php_v8_memory_limit_exception_methods);
250+
php_v8_memory_limit_exception_class_entry = zend_register_internal_class_ex(&ce, php_v8_abstract_resource_limit_exception_class_entry);
217251

218252
INIT_NS_CLASS_ENTRY(ce, PHP_V8_NS "\\Exceptions", "ValueException", php_v8_value_exception_methods);
219253
php_v8_value_exception_class_entry = zend_register_internal_class_ex(&ce, php_v8_generic_exception_class_entry);

src/php_v8_exceptions.h

+11
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ extern "C" {
3232
extern zend_class_entry* php_v8_generic_exception_class_entry;
3333
extern zend_class_entry* php_v8_try_catch_exception_class_entry;
3434
extern zend_class_entry* php_v8_termination_exception_class_entry;
35+
extern zend_class_entry* php_v8_abstract_resource_limit_exception_class_entry;
36+
extern zend_class_entry* php_v8_time_limit_exception_class_entry;
37+
extern zend_class_entry* php_v8_memory_limit_exception_class_entry;
3538

3639
extern zend_class_entry* php_v8_value_exception_class_entry;
3740
extern zend_class_entry* php_v8_script_exception_class_entry;
@@ -51,6 +54,8 @@ extern void php_v8_throw_try_catch_exception(php_v8_context_t *php_v8_context, v
5154
#define PHP_V8_CATCH_END() } assert(!try_catch.HasCaught());
5255

5356
#define PHP_V8_MAYBE_CATCH(php_v8_context, try_catch) \
57+
php_v8_isolate_maybe_update_limits_hit((php_v8_context)->php_v8_isolate);\
58+
php_v8_isolate_limits_maybe_stop_timer((php_v8_context)->php_v8_isolate);\
5459
if ((try_catch).HasCaught()) { \
5560
php_v8_throw_try_catch_exception((php_v8_context), &(try_catch)); \
5661
return; \
@@ -95,6 +100,12 @@ extern void php_v8_throw_try_catch_exception(php_v8_context_t *php_v8_context, v
95100
return; \
96101
}
97102

103+
#define PHP_V8_THROW_EXCEPTION_WHEN_LIMITS_HIT(php_v8_context) \
104+
if ((php_v8_context)->php_v8_isolate->limits.time_limit_hit || (php_v8_context)->php_v8_isolate->limits.memory_limit_hit) { \
105+
php_v8_throw_try_catch_exception((php_v8_context), NULL); \
106+
return; \
107+
}
108+
98109
#define PHP_V8_EMPTY_HANDLER_MSG_PART " is empty. Forgot to call parent::__construct()?"
99110
//#define PHP_V8_CHECK_EMPTY_HANDLER(val, message) if (NULL == (val)->php_v8_isolate || (val)->persistent->IsEmpty()) { PHP_V8_THROW_EXCEPTION(message); return; }
100111
// we check handler to be !IsEmpty() in constructors and before value creations, so unless we didn't check that by mistacke, IsEmpty() check may be skipped

src/php_v8_function.cc

+2
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ static PHP_METHOD(V8Function, NewInstance) {
203203
v8::Local<v8::Function> local_function = php_v8_value_get_function_local(isolate, php_v8_value);
204204

205205
PHP_V8_TRY_CATCH(isolate);
206+
PHP_V8_INIT_ISOLATE_LIMITS_ON_CONTEXT(php_v8_context);
206207

207208
v8::MaybeLocal<v8::Object> maybe_local_obj = local_function->NewInstance(context, argc, argv);
208209

@@ -248,6 +249,7 @@ static PHP_METHOD(V8Function, Call) {
248249
v8::Local<v8::Function> local_function = php_v8_value_get_function_local(isolate, php_v8_value);
249250

250251
PHP_V8_TRY_CATCH(isolate);
252+
PHP_V8_INIT_ISOLATE_LIMITS_ON_CONTEXT(php_v8_context);
251253

252254
v8::MaybeLocal<v8::Value> maybe_local_res = local_function->Call(context, local_recv, argc, argv);
253255

src/php_v8_isolate.cc

+140
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include "php_v8_a.h"
2828
#include "php_v8.h"
2929

30+
#include <float.h>
31+
3032

3133
zend_class_entry *php_v8_isolate_class_entry;
3234
#define this_ce php_v8_isolate_class_entry
@@ -130,6 +132,8 @@ static HashTable * php_v8_isolate_gc(zval *object, zval **table, int *n) {
130132
static void php_v8_isolate_free(zend_object *object) {
131133
php_v8_isolate_t *php_v8_isolate = php_v8_isolate_fetch_object(object);
132134

135+
php_v8_isolate_limits_free(php_v8_isolate);
136+
133137
if (php_v8_isolate->weak_function_templates) {
134138
php_v8_isolate_clean_weak<v8::FunctionTemplate>(php_v8_isolate->weak_function_templates);
135139
delete php_v8_isolate->weak_function_templates;
@@ -189,6 +193,8 @@ static zend_object *php_v8_isolate_ctor(zend_class_entry *ce) {
189193

190194
php_v8_isolate->std.handlers = &php_v8_isolate_object_handlers;
191195

196+
php_v8_isolate_limits_ctor(php_v8_isolate);
197+
192198
return &php_v8_isolate->std;
193199
}
194200

@@ -240,6 +246,106 @@ static PHP_METHOD(V8Isolate, __construct) {
240246
php_v8_isolate->isolate->SetFatalErrorHandler(php_v8_fatal_error_handler);
241247
}
242248

249+
static PHP_METHOD(V8Isolate, SetTimeLimit) {
250+
double time_limit_in_seconds;
251+
252+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &time_limit_in_seconds) == FAILURE) {
253+
return;
254+
}
255+
256+
PHP_V8_ISOLATE_FETCH_WITH_CHECK(getThis(), php_v8_isolate);
257+
258+
if (time_limit_in_seconds < 0) {
259+
PHP_V8_THROW_EXCEPTION("Time limit should be a non-negative float");
260+
return;
261+
}
262+
263+
php_v8_isolate_limits_set_time_limit(php_v8_isolate, time_limit_in_seconds);
264+
265+
zend_update_property_double(this_ce, getThis(), ZEND_STRL("time_limit"), time_limit_in_seconds);
266+
zend_update_property_bool(this_ce, getThis(), ZEND_STRL("time_limit_hit"), 0);
267+
}
268+
269+
static PHP_METHOD(V8Isolate, GetTimeLimit) {
270+
zval rv;
271+
272+
zval *prop = NULL;
273+
if (zend_parse_parameters_none() == FAILURE) {
274+
return;
275+
}
276+
277+
PHP_V8_ISOLATE_FETCH_WITH_CHECK(getThis(), php_v8_isolate);
278+
279+
prop = zend_read_property(this_ce, getThis(), ZEND_STRL("time_limit"), 0, &rv);
280+
281+
RETVAL_ZVAL(prop, 1, 0);
282+
}
283+
284+
static PHP_METHOD(V8Isolate, IsTimeLimitHit) {
285+
zval rv;
286+
287+
zval *prop = NULL;
288+
if (zend_parse_parameters_none() == FAILURE) {
289+
return;
290+
}
291+
292+
PHP_V8_ISOLATE_FETCH_WITH_CHECK(getThis(), php_v8_isolate);
293+
294+
prop = zend_read_property(this_ce, getThis(), ZEND_STRL("time_limit_hit"), 0, &rv);
295+
296+
RETVAL_ZVAL(prop, 1, 0);
297+
}
298+
299+
static PHP_METHOD(V8Isolate, SetMemoryLimit) {
300+
long memory_limit_in_bytes;
301+
302+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &memory_limit_in_bytes) == FAILURE) {
303+
return;
304+
}
305+
306+
PHP_V8_ISOLATE_FETCH_WITH_CHECK(getThis(), php_v8_isolate);
307+
308+
if (memory_limit_in_bytes < 0) {
309+
PHP_V8_THROW_EXCEPTION("Memory limit should be a non-negative numeric value");
310+
return;
311+
}
312+
313+
php_v8_isolate_limits_set_memory_limit(php_v8_isolate, static_cast<size_t>(memory_limit_in_bytes));
314+
315+
zend_update_property_long(this_ce, getThis(), ZEND_STRL("memory_limit"), memory_limit_in_bytes);
316+
zend_update_property_bool(this_ce, getThis(), ZEND_STRL("memory_limit_hit"), 0);
317+
}
318+
319+
static PHP_METHOD(V8Isolate, GetMemoryLimit) {
320+
zval rv;
321+
322+
zval *prop = NULL;
323+
if (zend_parse_parameters_none() == FAILURE) {
324+
return;
325+
}
326+
327+
PHP_V8_ISOLATE_FETCH_WITH_CHECK(getThis(), php_v8_isolate);
328+
329+
prop = zend_read_property(this_ce, getThis(), ZEND_STRL("memory_limit"), 0, &rv);
330+
331+
RETVAL_ZVAL(prop, 1, 0);
332+
}
333+
334+
static PHP_METHOD(V8Isolate, IsMemoryLimitHit) {
335+
zval rv;
336+
337+
zval *prop = NULL;
338+
if (zend_parse_parameters_none() == FAILURE) {
339+
return;
340+
}
341+
342+
PHP_V8_ISOLATE_FETCH_WITH_CHECK(getThis(), php_v8_isolate);
343+
344+
prop = zend_read_property(this_ce, getThis(), ZEND_STRL("memory_limit_hit"), 0, &rv);
345+
346+
RETVAL_ZVAL(prop, 1, 0);
347+
}
348+
243349
static PHP_METHOD(V8Isolate, GetSnapshot) {
244350
zval rv;
245351

@@ -434,6 +540,26 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_v8_isolate___construct, ZEND_SEND_BY_VAL, ZEND_RE
434540
ZEND_ARG_OBJ_INFO(0, snapshot, v8\\StartupData, 1)
435541
ZEND_END_ARG_INFO()
436542

543+
ZEND_BEGIN_ARG_INFO_EX(arginfo_v8_isolate_SetTimeLimit, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 1)
544+
ZEND_ARG_TYPE_INFO(0, time_limit_in_seconds, IS_DOUBLE, 0)
545+
ZEND_END_ARG_INFO()
546+
547+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_v8_isolate_GetTimeLimit, ZEND_RETURN_VALUE, 0, IS_DOUBLE, NULL, 0)
548+
ZEND_END_ARG_INFO()
549+
550+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_v8_isolate_IsTimeLimitHit, ZEND_RETURN_VALUE, 0, _IS_BOOL, NULL, 0)
551+
ZEND_END_ARG_INFO()
552+
553+
ZEND_BEGIN_ARG_INFO_EX(arginfo_v8_isolate_SetMemoryLimit, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 1)
554+
ZEND_ARG_TYPE_INFO(0, memory_limit_in_bytes, IS_LONG, 0)
555+
ZEND_END_ARG_INFO()
556+
557+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_v8_isolate_GetMemoryLimit, ZEND_RETURN_VALUE, 0, IS_LONG, NULL, 0)
558+
ZEND_END_ARG_INFO()
559+
560+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_v8_isolate_IsMemoryLimitHit, ZEND_RETURN_VALUE, 0, _IS_BOOL, NULL, 0)
561+
ZEND_END_ARG_INFO()
562+
437563
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_v8_isolate_GetSnapshot, ZEND_RETURN_VALUE, 0, IS_OBJECT, "v8\\StartupData", 1)
438564
ZEND_END_ARG_INFO()
439565

@@ -485,6 +611,14 @@ ZEND_END_ARG_INFO()
485611
static const zend_function_entry php_v8_isolate_methods[] = {
486612
PHP_ME(V8Isolate, __construct, arginfo_v8_isolate___construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
487613

614+
PHP_ME(V8Isolate, SetTimeLimit, arginfo_v8_isolate_SetTimeLimit, ZEND_ACC_PUBLIC)
615+
PHP_ME(V8Isolate, GetTimeLimit, arginfo_v8_isolate_GetTimeLimit, ZEND_ACC_PUBLIC)
616+
PHP_ME(V8Isolate, IsTimeLimitHit, arginfo_v8_isolate_IsTimeLimitHit, ZEND_ACC_PUBLIC)
617+
618+
PHP_ME(V8Isolate, SetMemoryLimit, arginfo_v8_isolate_SetMemoryLimit, ZEND_ACC_PUBLIC)
619+
PHP_ME(V8Isolate, GetMemoryLimit, arginfo_v8_isolate_GetMemoryLimit, ZEND_ACC_PUBLIC)
620+
PHP_ME(V8Isolate, IsMemoryLimitHit, arginfo_v8_isolate_IsMemoryLimitHit, ZEND_ACC_PUBLIC)
621+
488622
PHP_ME(V8Isolate, GetSnapshot, arginfo_v8_isolate_GetSnapshot, ZEND_ACC_PUBLIC)
489623

490624
PHP_ME(V8Isolate, GetHeapStatistics, arginfo_v8_isolate_GetHeapStatistics, ZEND_ACC_PUBLIC)
@@ -517,6 +651,12 @@ PHP_MINIT_FUNCTION (php_v8_isolate) {
517651

518652
zend_declare_property_null(this_ce, ZEND_STRL("snapshot"), ZEND_ACC_PRIVATE);
519653

654+
zend_declare_property_double(this_ce, ZEND_STRL("time_limit"), 0.0, ZEND_ACC_PRIVATE);
655+
zend_declare_property_bool(this_ce, ZEND_STRL("time_limit_hit"), 0, ZEND_ACC_PRIVATE);
656+
657+
zend_declare_property_long(this_ce, ZEND_STRL("memory_limit"), 0, ZEND_ACC_PRIVATE);
658+
zend_declare_property_bool(this_ce, ZEND_STRL("memory_limit_hit"), 0, ZEND_ACC_PRIVATE);
659+
520660
memcpy(&php_v8_isolate_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
521661

522662
php_v8_isolate_object_handlers.offset = XtOffsetOf(php_v8_isolate_t, std);

src/php_v8_isolate.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
typedef struct _php_v8_isolate_t php_v8_isolate_t;
1919

20+
#include "php_v8_isolate_limits.h"
2021
#include "php_v8_exceptions.h"
21-
2222
#include "php_v8_callbacks.h"
2323
#include <v8.h>
2424
#include <map>
@@ -31,12 +31,13 @@ extern "C" {
3131
#endif
3232
}
3333

34+
extern zend_class_entry *php_v8_isolate_class_entry;
3435

3536
extern php_v8_isolate_t * php_v8_isolate_fetch_object(zend_object *obj);
3637

3738
// TODO: remove or cleanup to use for debug reasons
38-
//#define SX(x) #x
39-
//#define SX_(x) S(x)
39+
#define SX(x) #x
40+
#define SX_(x) S(x)
4041
//#define S__LINE__ SX_(__LINE__)
4142
//#define S__FILE__ SX_(__FILE__)
4243
//#define PHP_V8_ISOLATES_CHECK(first, second) if ((first)->isolate != (second)->isolate) { PHP_V8_THROW_EXCEPTION("Isolates mismatch: " S__FILE__ ":" S__LINE__); return; }
@@ -136,6 +137,7 @@ struct _php_v8_isolate_t {
136137
std::map<v8::Persistent<v8::Value>*, php_v8_callbacks_t *> *weak_values;
137138

138139
uint32_t isolate_handle;
140+
php_v8_isolate_limits_t limits;
139141

140142
zval *gc_data;
141143
int gc_data_count;

0 commit comments

Comments
 (0)