提交 1435b28b 编写于 作者: H hjdhnx

升级3.9.3

上级 67e298dc
......@@ -7,7 +7,7 @@ import json
import os
from flask import Blueprint,abort,render_template,render_template_string,url_for,redirect,make_response,send_from_directory
from flask import Blueprint,abort,render_template,render_template_string,url_for,redirect,make_response,send_from_directory,request
from controllers.service import storage_service,rules_service
from controllers.classes import getClasses,getClassInfo
......@@ -202,6 +202,9 @@ def merged_hide(merged_config):
@home.route('/config/<int:mode>')
def config_render(mode):
# print(dict(app.config))
UA = request.headers['User-Agent']
ISTVB = 'okhttp/3' in UA
logger.info(UA)
if mode == 1:
jyw_ip = getHost(mode)
logger.info(jyw_ip)
......@@ -229,7 +232,7 @@ def config_render(mode):
rules = getRules('js',js_mode)
rules = get_multi_rules(rules)
# html = render_template('config.txt',rules=getRules('js'),host=host,mode=mode,jxs=jxs,base64Encode=base64Encode,config=new_conf)
html = render_template('config.txt',pys=pys,rules=rules,host=host,mode=mode,js_mode=js_mode,jxs=jxs,alists=alists,alists_str=alists_str,live_url=live_url,config=new_conf)
html = render_template('config.txt',UA=UA,ISTVB=ISTVB,pys=pys,rules=rules,host=host,mode=mode,js_mode=js_mode,jxs=jxs,alists=alists,alists_str=alists_str,live_url=live_url,config=new_conf)
merged_config = custom_merge(parseText(html),customConfig)
# print(merged_config['sites'])
......
3.9.2
\ No newline at end of file
3.9.3
\ No newline at end of file
import './util-ym.js'
import dr from './drpy.js'
__JS_SPIDER__ = dr.DRPY()
\ No newline at end of file
#include <Python.h>
#include <time.h>
#include "third-party/quickjs.h"
// Node of Python callable that the context needs to keep available.
typedef struct PythonCallableNode PythonCallableNode;
struct PythonCallableNode {
PyObject *obj;
// Internal ID of the callable function. "magic" is QuickJS terminology.
int magic;
PythonCallableNode *next;
};
// Keeps track of the time if we are using a time limit.
typedef struct {
clock_t start;
clock_t limit;
} InterruptData;
// The data of the type _quickjs.Context.
typedef struct {
PyObject_HEAD JSRuntime *runtime;
JSContext *context;
int has_time_limit;
clock_t time_limit;
// Used when releasing the GIL.
PyThreadState *thread_state;
InterruptData interrupt_data;
// NULL-terminated singly linked list of callable Python objects that we need to keep alive.
PythonCallableNode *python_callables;
} ContextData;
// The data of the type _quickjs.Object.
typedef struct {
PyObject_HEAD;
ContextData *context;
JSValue object;
} ObjectData;
// The exception raised by this module.
static PyObject *JSException = NULL;
static PyObject *StackOverflow = NULL;
// Converts a JSValue to a Python object.
//
// Takes ownership of the JSValue and will deallocate it (refcount reduced by 1).
static PyObject *quickjs_to_python(ContextData *context_obj, JSValue value);
// Whether converting item to QuickJS would be possible.
static int python_to_quickjs_possible(ContextData *context, PyObject *item);
// Converts item to QuickJS.
//
// If the Python object is not possible to convert to JS, undefined will be returned. This fallback
// will not be used if python_to_quickjs_possible returns 1.
static JSValueConst python_to_quickjs(ContextData *context, PyObject *item);
static PyTypeObject Object;
// Returns nonzero if we should stop due to a time limit.
static int js_interrupt_handler(JSRuntime *rt, void *opaque) {
InterruptData *data = opaque;
if (clock() - data->start >= data->limit) {
return 1;
} else {
return 0;
}
}
// Sets up a context and an InterruptData struct if the context has a time limit.
static void setup_time_limit(ContextData *context, InterruptData *interrupt_data) {
if (context->has_time_limit) {
JS_SetInterruptHandler(context->runtime, js_interrupt_handler, interrupt_data);
interrupt_data->limit = context->time_limit;
interrupt_data->start = clock();
}
}
// Restores the context if the context has a time limit.
static void teardown_time_limit(ContextData *context) {
if (context->has_time_limit) {
JS_SetInterruptHandler(context->runtime, NULL, NULL);
}
}
// This method is always called in a context before running JS code in QuickJS. It sets up time
// limites, releases the GIL etc.
static void prepare_call_js(ContextData *context) {
// We release the GIL in order to speed things up for certain use cases.
assert(!context->thread_state);
context->thread_state = PyEval_SaveThread();
setup_time_limit(context, &context->interrupt_data);
}
// This method is called right after returning from running JS code. Aquires the GIL etc.
static void end_call_js(ContextData *context) {
teardown_time_limit(context);
assert(context->thread_state);
PyEval_RestoreThread(context->thread_state);
context->thread_state = NULL;
}
// Called when Python is called again from inside QuickJS.
static void prepare_call_python(ContextData *context) {
assert(context->thread_state);
PyEval_RestoreThread(context->thread_state);
context->thread_state = NULL;
}
// Called when the operation started by prepare_call_python is done.
static void end_call_python(ContextData *context) {
assert(!context->thread_state);
context->thread_state = PyEval_SaveThread();
}
// GC traversal.
static int object_traverse(ObjectData *self, visitproc visit, void *arg) {
Py_VISIT(self->context);
return 0;
}
// Creates an instance of the Object class.
static PyObject *object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
ObjectData *self = PyObject_GC_New(ObjectData, type);
if (self != NULL) {
self->context = NULL;
}
return (PyObject *)self;
}
// Deallocates an instance of the Object class.
static void object_dealloc(ObjectData *self) {
if (self->context) {
PyObject_GC_UnTrack(self);
JS_FreeValue(self->context->context, self->object);
// We incremented the refcount of the context when we created this object, so we should
// decrease it now so we don't leak memory.
Py_DECREF(self->context);
}
PyObject_GC_Del(self);
}
// _quickjs.Object.get
//
// Gets a Javascript property of the object.
static PyObject *object_get(ObjectData *self, PyObject *args) {
const char *name;
if (!PyArg_ParseTuple(args, "s", &name)) {
return NULL;
}
JSValue value = JS_GetPropertyStr(self->context->context, self->object, name);
return quickjs_to_python(self->context, value);
}
static JSValue js_c_function(
JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
ContextData *context = (ContextData *)JS_GetContextOpaque(ctx);
if (context->has_time_limit) {
return JS_ThrowInternalError(ctx, "Can not call into Python with a time limit set.");
}
PythonCallableNode *node = context->python_callables;
while (node && node->magic != magic) {
node = node->next;
}
if (!node) {
return JS_ThrowInternalError(ctx, "Internal error.");
}
prepare_call_python(context);
PyObject *args = PyTuple_New(argc);
if (!args) {
end_call_python(context);
return JS_ThrowOutOfMemory(ctx);
}
int tuple_success = 1;
for (int i = 0; i < argc; ++i) {
PyObject *arg = quickjs_to_python(context, JS_DupValue(ctx, argv[i]));
if (!arg) {
tuple_success = 0;
break;
}
PyTuple_SET_ITEM(args, i, arg);
}
if (!tuple_success) {
Py_DECREF(args);
end_call_python(context);
return JS_ThrowInternalError(ctx, "Internal error: could not convert args.");
}
PyObject *result = PyObject_CallObject(node->obj, args);
Py_DECREF(args);
if (!result) {
end_call_python(context);
return JS_ThrowInternalError(ctx, "Python call failed.");
}
JSValue js_result = JS_NULL;
if (python_to_quickjs_possible(context, result)) {
js_result = python_to_quickjs(context, result);
} else {
PyErr_Clear();
js_result = JS_ThrowInternalError(ctx, "Can not convert Python result to JS.");
}
Py_DECREF(result);
end_call_python(context);
return js_result;
}
// _quickjs.Object.set
//
// Sets a Javascript property to the object. Callables are supported.
static PyObject *object_set(ObjectData *self, PyObject *args) {
const char *name;
PyObject *item;
if (!PyArg_ParseTuple(args, "sO", &name, &item)) {
return NULL;
}
int ret = 0;
if (PyCallable_Check(item) && (!PyObject_IsInstance(item, (PyObject *)&Object) || JS_IsFunction(
self->context->context, ((ObjectData *)item)->object))) {
PythonCallableNode *node = PyMem_Malloc(sizeof(PythonCallableNode));
if (!node) {
return NULL;
}
Py_INCREF(item);
node->magic = 0;
if (self->context->python_callables) {
node->magic = self->context->python_callables->magic + 1;
}
node->obj = item;
node->next = self->context->python_callables;
self->context->python_callables = node;
JSValue function = JS_NewCFunctionMagic(
self->context->context,
js_c_function,
name,
0, // TODO: Should we allow setting the .length of the function to something other than 0?
JS_CFUNC_generic_magic,
node->magic);
// If this fails we don't notify the caller of this function.
ret = JS_SetPropertyStr(self->context->context, self->object, name, function);
if (ret != 1) {
PyErr_SetString(PyExc_TypeError, "Failed setting the variable as a callable.");
return NULL;
} else {
Py_RETURN_NONE;
}
} else {
if (python_to_quickjs_possible(self->context, item)) {
ret = JS_SetPropertyStr(self->context->context, self->object, name,
python_to_quickjs(self->context, item));
if (ret != 1) {
PyErr_SetString(PyExc_TypeError, "Failed setting the variable.");
}
}
if (ret == 1) {
Py_RETURN_NONE;
} else {
return NULL;
}
}
}
// _quickjs.Object.__call__
static PyObject *object_call(ObjectData *self, PyObject *args, PyObject *kwds);
// _quickjs.Object.json
//
// Returns the JSON representation of the object as a Python string.
static PyObject *object_json(ObjectData *self) {
JSContext *context = self->context->context;
JSValue json_string = JS_JSONStringify(context, self->object, JS_UNDEFINED, JS_UNDEFINED);
return quickjs_to_python(self->context, json_string);
}
// All methods of the _quickjs.Object class.
static PyMethodDef object_methods[] = {
{"get", (PyCFunction)object_get, METH_VARARGS, "Gets a Javascript property of the object."},
{"set", (PyCFunction)object_set, METH_VARARGS, "Sets a Javascript property to the object."},
{"json", (PyCFunction)object_json, METH_NOARGS, "Converts to a JSON string."},
{NULL} /* Sentinel */
};
// Define the quickjs.Object type.
static PyTypeObject Object = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Object",
.tp_doc = "Quickjs object",
.tp_basicsize = sizeof(ObjectData),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_traverse = (traverseproc)object_traverse,
.tp_new = object_new,
.tp_dealloc = (destructor)object_dealloc,
.tp_call = (ternaryfunc)object_call,
.tp_methods = object_methods};
// Whether converting item to QuickJS would be possible.
static int python_to_quickjs_possible(ContextData *context, PyObject *item) {
if (PyBool_Check(item)) {
return 1;
} else if (PyLong_Check(item)) {
return 1;
} else if (PyFloat_Check(item)) {
return 1;
} else if (item == Py_None) {
return 1;
} else if (PyUnicode_Check(item)) {
return 1;
} else if (PyObject_IsInstance(item, (PyObject *)&Object)) {
ObjectData *object = (ObjectData *)item;
if (object->context != context) {
PyErr_Format(PyExc_ValueError, "Can not mix JS objects from different contexts.");
return 0;
}
return 1;
} else {
PyErr_Format(PyExc_TypeError,
"Unsupported type when converting a Python object to quickjs: %s.",
Py_TYPE(item)->tp_name);
return 0;
}
}
// Converts item to QuickJS.
//
// If the Python object is not possible to convert to JS, undefined will be returned. This fallback
// will not be used if python_to_quickjs_possible returns 1.
static JSValueConst python_to_quickjs(ContextData *context, PyObject *item) {
if (PyBool_Check(item)) {
return JS_MKVAL(JS_TAG_BOOL, item == Py_True ? 1 : 0);
} else if (PyLong_Check(item)) {
int overflow;
long value = PyLong_AsLongAndOverflow(item, &overflow);
if (overflow) {
PyObject *float_value = PyNumber_Float(item);
double double_value = PyFloat_AsDouble(float_value);
Py_DECREF(float_value);
return JS_NewFloat64(context->context, double_value);
} else {
return JS_MKVAL(JS_TAG_INT, value);
}
} else if (PyFloat_Check(item)) {
return JS_NewFloat64(context->context, PyFloat_AsDouble(item));
} else if (item == Py_None) {
return JS_NULL;
} else if (PyUnicode_Check(item)) {
return JS_NewString(context->context, PyUnicode_AsUTF8(item));
} else if (PyObject_IsInstance(item, (PyObject *)&Object)) {
return JS_DupValue(context->context, ((ObjectData *)item)->object);
} else {
// Can not happen if python_to_quickjs_possible passes.
return JS_UNDEFINED;
}
}
// _quickjs.Object.__call__
static PyObject *object_call(ObjectData *self, PyObject *args, PyObject *kwds) {
if (self->context == NULL) {
// This object does not have a context and has not been created by this module.
Py_RETURN_NONE;
}
// We first loop through all arguments and check that they are supported without doing anything.
// This makes the cleanup code simpler for the case where we have to raise an error.
const int nargs = PyTuple_Size(args);
for (int i = 0; i < nargs; ++i) {
PyObject *item = PyTuple_GetItem(args, i);
if (!python_to_quickjs_possible(self->context, item)) {
return NULL;
}
}
// Now we know that all arguments are supported and we can convert them.
JSValueConst *jsargs = malloc(nargs * sizeof(JSValueConst));
for (int i = 0; i < nargs; ++i) {
PyObject *item = PyTuple_GetItem(args, i);
jsargs[i] = python_to_quickjs(self->context, item);
}
prepare_call_js(self->context);
JSValue value;
value = JS_Call(self->context->context, self->object, JS_NULL, nargs, jsargs);
for (int i = 0; i < nargs; ++i) {
JS_FreeValue(self->context->context, jsargs[i]);
}
free(jsargs);
end_call_js(self->context);
return quickjs_to_python(self->context, value);
}
// Converts the current Javascript exception to a Python exception via a C string.
static void quickjs_exception_to_python(JSContext *context) {
JSValue exception = JS_GetException(context);
const char *cstring = JS_ToCString(context, exception);
const char *stack_cstring = NULL;
if (!JS_IsNull(exception) && !JS_IsUndefined(exception)) {
JSValue stack = JS_GetPropertyStr(context, exception, "stack");
if (!JS_IsException(stack)) {
stack_cstring = JS_ToCString(context, stack);
JS_FreeValue(context, stack);
}
}
if (cstring != NULL) {
const char *safe_stack_cstring = stack_cstring ? stack_cstring : "";
if (strstr(cstring, "stack overflow") != NULL) {
PyErr_Format(StackOverflow, "%s\n%s", cstring, safe_stack_cstring);
} else {
PyErr_Format(JSException, "%s\n%s", cstring, safe_stack_cstring);
}
} else {
// This has been observed to happen when different threads have used the same QuickJS
// runtime, but not at the same time.
// Could potentially be another problem though, since JS_ToCString may return NULL.
PyErr_Format(JSException,
"(Failed obtaining QuickJS error string. Concurrency issue?)");
}
JS_FreeCString(context, cstring);
JS_FreeCString(context, stack_cstring);
JS_FreeValue(context, exception);
}
// Converts a JSValue to a Python object.
//
// Takes ownership of the JSValue and will deallocate it (refcount reduced by 1).
static PyObject *quickjs_to_python(ContextData *context_obj, JSValue value) {
JSContext *context = context_obj->context;
int tag = JS_VALUE_GET_TAG(value);
// A return value of NULL means an exception.
PyObject *return_value = NULL;
if (tag == JS_TAG_INT) {
return_value = Py_BuildValue("i", JS_VALUE_GET_INT(value));
} else if (tag == JS_TAG_BIG_INT) {
const char *cstring = JS_ToCString(context, value);
return_value = PyLong_FromString(cstring, NULL, 10);
JS_FreeCString(context, cstring);
} else if (tag == JS_TAG_BOOL) {
return_value = Py_BuildValue("O", JS_VALUE_GET_BOOL(value) ? Py_True : Py_False);
} else if (tag == JS_TAG_NULL) {
return_value = Py_None;
} else if (tag == JS_TAG_UNDEFINED) {
return_value = Py_None;
} else if (tag == JS_TAG_EXCEPTION) {
quickjs_exception_to_python(context);
} else if (tag == JS_TAG_FLOAT64) {
return_value = Py_BuildValue("d", JS_VALUE_GET_FLOAT64(value));
} else if (tag == JS_TAG_STRING) {
const char *cstring = JS_ToCString(context, value);
return_value = Py_BuildValue("s", cstring);
JS_FreeCString(context, cstring);
} else if (tag == JS_TAG_OBJECT || tag == JS_TAG_MODULE || tag == JS_TAG_SYMBOL) {
// This is a Javascript object or function. We wrap it in a _quickjs.Object.
return_value = PyObject_CallObject((PyObject *)&Object, NULL);
ObjectData *object = (ObjectData *)return_value;
// This is important. Otherwise, the context may be deallocated before the object, which
// will result in a segfault with high probability.
Py_INCREF(context_obj);
object->context = context_obj;
PyObject_GC_Track(object);
object->object = JS_DupValue(context, value);
} else {
PyErr_Format(PyExc_TypeError, "Unknown quickjs tag: %d", tag);
}
JS_FreeValue(context, value);
if (return_value == Py_None) {
// Can not simply return PyNone for refcounting reasons.
Py_RETURN_NONE;
}
return return_value;
}
static PyObject *test(PyObject *self, PyObject *args) {
return Py_BuildValue("i", 42);
}
// Global state of the module. Currently none.
struct module_state {};
// GC traversal.
static int context_traverse(ContextData *self, visitproc visit, void *arg) {
PythonCallableNode *node = self->python_callables;
while (node) {
Py_VISIT(node->obj);
node = node->next;
}
return 0;
}
// GC clearing. Object does not have a clearing method, therefore dependency cycles
// between Context and Object will always be cleared starting here.
static int context_clear(ContextData *self) {
PythonCallableNode *node = self->python_callables;
while (node) {
Py_CLEAR(node->obj);
node = node->next;
}
return 0;
}
// Creates an instance of the _quickjs.Context class.
static PyObject *context_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
ContextData *self = PyObject_GC_New(ContextData, type);
if (self != NULL) {
// We never have different contexts for the same runtime. This way, different
// _quickjs.Context can be used concurrently.
self->runtime = JS_NewRuntime();
self->context = JS_NewContext(self->runtime);
self->has_time_limit = 0;
self->time_limit = 0;
self->thread_state = NULL;
self->python_callables = NULL;
JS_SetContextOpaque(self->context, self);
PyObject_GC_Track(self);
}
return (PyObject *)self;
}
// Deallocates an instance of the _quickjs.Context class.
static void context_dealloc(ContextData *self) {
JS_FreeContext(self->context);
JS_FreeRuntime(self->runtime);
PyObject_GC_UnTrack(self);
PythonCallableNode *node = self->python_callables;
self->python_callables = NULL;
while (node) {
PythonCallableNode *this = node;
node = node->next;
// this->obj may already be NULL if GC'ed right before through context_clear.
Py_XDECREF(this->obj);
PyMem_Free(this);
}
PyObject_GC_Del(self);
}
// Evaluates a Python string as JS and returns the result as a Python object. Will return
// _quickjs.Object for complex types (other than e.g. str, int).
static PyObject *context_eval_internal(ContextData *self, PyObject *args, int eval_type) {
const char *code;
if (!PyArg_ParseTuple(args, "s", &code)) {
return NULL;
}
prepare_call_js(self);
JSValue value;
value = JS_Eval(self->context, code, strlen(code), "<input>", eval_type);
end_call_js(self);
return quickjs_to_python(self, value);
}
// _quickjs.Context.eval
//
// Evaluates a Python string as JS and returns the result as a Python object. Will return
// _quickjs.Object for complex types (other than e.g. str, int).
static PyObject *context_eval(ContextData *self, PyObject *args) {
return context_eval_internal(self, args, JS_EVAL_TYPE_GLOBAL);
}
// _quickjs.Context.module
//
// Evaluates a Python string as JS module. Otherwise identical to eval.
static PyObject *context_module(ContextData *self, PyObject *args) {
return context_eval_internal(self, args, JS_EVAL_TYPE_MODULE);
}
// _quickjs.Context.execute_pending_job
//
// If there are pending jobs, executes one and returns True. Else returns False.
static PyObject *context_execute_pending_job(ContextData *self) {
prepare_call_js(self);
JSContext *ctx;
int ret = JS_ExecutePendingJob(self->runtime, &ctx);
end_call_js(self);
if (ret > 0) {
Py_RETURN_TRUE;
} else if (ret == 0) {
Py_RETURN_FALSE;
} else {
quickjs_exception_to_python(ctx);
return NULL;
}
}
// _quickjs.Context.parse_json
//
// Evaluates a Python string as JSON and returns the result as a Python object. Will
// return _quickjs.Object for complex types (other than e.g. str, int).
static PyObject *context_parse_json(ContextData *self, PyObject *args) {
const char *data;
if (!PyArg_ParseTuple(args, "s", &data)) {
return NULL;
}
JSValue value;
Py_BEGIN_ALLOW_THREADS;
value = JS_ParseJSON(self->context, data, strlen(data), "context_parse_json.json");
Py_END_ALLOW_THREADS;
return quickjs_to_python(self, value);
}
// _quickjs.Context.get_global
//
// Retrieves the global object of the JS context.
static PyObject *context_get_global(ContextData *self) {
return quickjs_to_python(self, JS_GetGlobalObject(self->context));
}
// _quickjs.Context.get
//
// Retrieves a global variable from the JS context.
static PyObject *context_get(ContextData *self, PyObject *args) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Context.get is deprecated, use Context.get_global().get instead.", 1);
PyObject *global = context_get_global(self);
if (global == NULL) {
return NULL;
}
PyObject *ret = object_get((ObjectData *)global, args);
Py_DECREF(global);
return ret;
}
// _quickjs.Context.set
//
// Sets a global variable to the JS context.
static PyObject *context_set(ContextData *self, PyObject *args) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Context.set is deprecated, use Context.get_global().set instead.", 1);
PyObject *global = context_get_global(self);
if (global == NULL) {
return NULL;
}
PyObject *ret = object_set((ObjectData *)global, args);
Py_DECREF(global);
return ret;
}
// _quickjs.Context.set_memory_limit
//
// Sets the memory limit of the context.
static PyObject *context_set_memory_limit(ContextData *self, PyObject *args) {
Py_ssize_t limit;
if (!PyArg_ParseTuple(args, "n", &limit)) {
return NULL;
}
JS_SetMemoryLimit(self->runtime, limit);
Py_RETURN_NONE;
}
// _quickjs.Context.set_time_limit
//
// Sets the CPU time limit of the context. This will be used in an interrupt handler.
static PyObject *context_set_time_limit(ContextData *self, PyObject *args) {
double limit;
if (!PyArg_ParseTuple(args, "d", &limit)) {
return NULL;
}
if (limit < 0) {
self->has_time_limit = 0;
} else {
self->has_time_limit = 1;
self->time_limit = (clock_t)(limit * CLOCKS_PER_SEC);
}
Py_RETURN_NONE;
}
// _quickjs.Context.set_max_stack_size
//
// Sets the max stack size in bytes.
static PyObject *context_set_max_stack_size(ContextData *self, PyObject *args) {
Py_ssize_t limit;
if (!PyArg_ParseTuple(args, "n", &limit)) {
return NULL;
}
JS_SetMaxStackSize(self->runtime, limit);
Py_RETURN_NONE;
}
// _quickjs.Context.memory
//
// Sets the CPU time limit of the context. This will be used in an interrupt handler.
static PyObject *context_memory(ContextData *self) {
PyObject *dict = PyDict_New();
if (dict == NULL) {
return NULL;
}
JSMemoryUsage usage;
JS_ComputeMemoryUsage(self->runtime, &usage);
#define MEM_USAGE_ADD_TO_DICT(key) \
{ \
PyObject *value = PyLong_FromLongLong(usage.key); \
if (PyDict_SetItemString(dict, #key, value) != 0) { \
return NULL; \
} \
Py_DECREF(value); \
}
MEM_USAGE_ADD_TO_DICT(malloc_size);
MEM_USAGE_ADD_TO_DICT(malloc_limit);
MEM_USAGE_ADD_TO_DICT(memory_used_size);
MEM_USAGE_ADD_TO_DICT(malloc_count);
MEM_USAGE_ADD_TO_DICT(memory_used_count);
MEM_USAGE_ADD_TO_DICT(atom_count);
MEM_USAGE_ADD_TO_DICT(atom_size);
MEM_USAGE_ADD_TO_DICT(str_count);
MEM_USAGE_ADD_TO_DICT(str_size);
MEM_USAGE_ADD_TO_DICT(obj_count);
MEM_USAGE_ADD_TO_DICT(obj_size);
MEM_USAGE_ADD_TO_DICT(prop_count);
MEM_USAGE_ADD_TO_DICT(prop_size);
MEM_USAGE_ADD_TO_DICT(shape_count);
MEM_USAGE_ADD_TO_DICT(shape_size);
MEM_USAGE_ADD_TO_DICT(js_func_count);
MEM_USAGE_ADD_TO_DICT(js_func_size);
MEM_USAGE_ADD_TO_DICT(js_func_code_size);
MEM_USAGE_ADD_TO_DICT(js_func_pc2line_count);
MEM_USAGE_ADD_TO_DICT(js_func_pc2line_size);
MEM_USAGE_ADD_TO_DICT(c_func_count);
MEM_USAGE_ADD_TO_DICT(array_count);
MEM_USAGE_ADD_TO_DICT(fast_array_count);
MEM_USAGE_ADD_TO_DICT(fast_array_elements);
MEM_USAGE_ADD_TO_DICT(binary_object_count);
MEM_USAGE_ADD_TO_DICT(binary_object_size);
return dict;
}
// _quickjs.Context.gc
//
// Runs garbage collection.
static PyObject *context_gc(ContextData *self) {
JS_RunGC(self->runtime);
Py_RETURN_NONE;
}
static PyObject *context_add_callable(ContextData *self, PyObject *args) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Context.add_callable is deprecated, use Context.get_global().set instead.", 1);
PyObject *global = context_get_global(self);
if (global == NULL) {
return NULL;
}
PyObject *ret = object_set((ObjectData *)global, args);
Py_DECREF(global);
return ret;
}
// All methods of the _quickjs.Context class.
static PyMethodDef context_methods[] = {
{"eval", (PyCFunction)context_eval, METH_VARARGS, "Evaluates a Javascript string."},
{"module",
(PyCFunction)context_module,
METH_VARARGS,
"Evaluates a Javascript string as a module."},
{"execute_pending_job", (PyCFunction)context_execute_pending_job, METH_NOARGS, "Executes a pending job."},
{"parse_json", (PyCFunction)context_parse_json, METH_VARARGS, "Parses a JSON string."},
{"get_global", (PyCFunction)context_get_global, METH_NOARGS, "Gets the Javascript global object."},
{"get", (PyCFunction)context_get, METH_VARARGS, "Gets a Javascript global variable."},
{"set", (PyCFunction)context_set, METH_VARARGS, "Sets a Javascript global variable."},
{"set_memory_limit",
(PyCFunction)context_set_memory_limit,
METH_VARARGS,
"Sets the memory limit in bytes."},
{"set_time_limit",
(PyCFunction)context_set_time_limit,
METH_VARARGS,
"Sets the CPU time limit in seconds (C function clock() is used)."},
{"set_max_stack_size",
(PyCFunction)context_set_max_stack_size,
METH_VARARGS,
"Sets the maximum stack size in bytes. Default is 256kB."},
{"memory", (PyCFunction)context_memory, METH_NOARGS, "Returns the memory usage as a dict."},
{"gc", (PyCFunction)context_gc, METH_NOARGS, "Runs garbage collection."},
{"add_callable", (PyCFunction)context_add_callable, METH_VARARGS, "Wraps a Python callable."},
{NULL} /* Sentinel */
};
// Define the _quickjs.Context type.
static PyTypeObject Context = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Context",
.tp_doc = "Quickjs context",
.tp_basicsize = sizeof(ContextData),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_traverse = (traverseproc)context_traverse,
.tp_clear = (inquiry)context_clear,
.tp_new = context_new,
.tp_dealloc = (destructor)context_dealloc,
.tp_methods = context_methods};
// All global methods in _quickjs.
static PyMethodDef myextension_methods[] = {{"test", (PyCFunction)test, METH_NOARGS, NULL},
{NULL, NULL}};
// Define the _quickjs module.
static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
"quickjs",
NULL,
sizeof(struct module_state),
myextension_methods,
NULL,
NULL,
NULL,
NULL};
// This function runs when the module is first imported.
PyMODINIT_FUNC PyInit__quickjs(void) {
if (PyType_Ready(&Context) < 0) {
return NULL;
}
if (PyType_Ready(&Object) < 0) {
return NULL;
}
PyObject *module = PyModule_Create(&moduledef);
if (module == NULL) {
return NULL;
}
JSException = PyErr_NewException("_quickjs.JSException", NULL, NULL);
if (JSException == NULL) {
return NULL;
}
StackOverflow = PyErr_NewException("_quickjs.StackOverflow", JSException, NULL);
if (StackOverflow == NULL) {
return NULL;
}
Py_INCREF(&Context);
PyModule_AddObject(module, "Context", (PyObject *)&Context);
Py_INCREF(&Object);
PyModule_AddObject(module, "Object", (PyObject *)&Object);
PyModule_AddObject(module, "JSException", JSException);
PyModule_AddObject(module, "StackOverflow", StackOverflow);
return module;
}
import concurrent.futures
import json
import unittest
import quickjs
class LoadModule(unittest.TestCase):
def test_42(self):
self.assertEqual(quickjs.test(), 42)
class Context(unittest.TestCase):
def setUp(self):
self.context = quickjs.Context()
def test_eval_int(self):
self.assertEqual(self.context.eval("40 + 2"), 42)
def test_eval_float(self):
self.assertEqual(self.context.eval("40.0 + 2.0"), 42.0)
def test_eval_str(self):
self.assertEqual(self.context.eval("'4' + '2'"), "42")
def test_eval_bool(self):
self.assertEqual(self.context.eval("true || false"), True)
self.assertEqual(self.context.eval("true && false"), False)
def test_eval_null(self):
self.assertIsNone(self.context.eval("null"))
def test_eval_undefined(self):
self.assertIsNone(self.context.eval("undefined"))
def test_wrong_type(self):
with self.assertRaises(TypeError):
self.assertEqual(self.context.eval(1), 42)
def test_context_between_calls(self):
self.context.eval("x = 40; y = 2;")
self.assertEqual(self.context.eval("x + y"), 42)
def test_function(self):
self.context.eval("""
function special(x) {
return 40 + x;
}
""")
self.assertEqual(self.context.eval("special(2)"), 42)
def test_get(self):
self.context.eval("x = 42; y = 'foo';")
self.assertEqual(self.context.get("x"), 42)
self.assertEqual(self.context.get("y"), "foo")
self.assertEqual(self.context.get("z"), None)
def test_set(self):
self.context.eval("x = 'overriden'")
self.context.set("x", 42)
self.context.set("y", "foo")
self.assertTrue(self.context.eval("x == 42"))
self.assertTrue(self.context.eval("y == 'foo'"))
def test_module(self):
self.context.module("""
export function test() {
return 42;
}
""")
def test_error(self):
with self.assertRaisesRegex(quickjs.JSException, "ReferenceError: 'missing' is not defined"):
self.context.eval("missing + missing")
def test_lifetime(self):
def get_f():
context = quickjs.Context()
f = context.eval("""
a = function(x) {
return 40 + x;
}
""")
return f
f = get_f()
self.assertTrue(f)
# The context has left the scope after f. f needs to keep the context alive for the
# its lifetime. Otherwise, we will get problems.
def test_backtrace(self):
try:
self.context.eval("""
function funcA(x) {
x.a.b = 1;
}
function funcB(x) {
funcA(x);
}
funcB({});
""")
except Exception as e:
msg = str(e)
else:
self.fail("Expected exception.")
self.assertIn("at funcA (<input>:3)\n", msg)
self.assertIn("at funcB (<input>:6)\n", msg)
def test_memory_limit(self):
code = """
(function() {
let arr = [];
for (let i = 0; i < 1000; ++i) {
arr.push(i);
}
})();
"""
self.context.eval(code)
self.context.set_memory_limit(1000)
with self.assertRaisesRegex(quickjs.JSException, "null"):
self.context.eval(code)
self.context.set_memory_limit(1000000)
self.context.eval(code)
def test_time_limit(self):
code = """
(function() {
let arr = [];
for (let i = 0; i < 100000; ++i) {
arr.push(i);
}
return arr;
})();
"""
self.context.eval(code)
self.context.set_time_limit(0)
with self.assertRaisesRegex(quickjs.JSException, "InternalError: interrupted"):
self.context.eval(code)
self.context.set_time_limit(-1)
self.context.eval(code)
def test_memory_usage(self):
self.assertIn("memory_used_size", self.context.memory().keys())
def test_json_simple(self):
self.assertEqual(self.context.parse_json("42"), 42)
def test_json_error(self):
with self.assertRaisesRegex(quickjs.JSException, "unexpected token"):
self.context.parse_json("a b c")
def test_execute_pending_job(self):
self.context.eval("obj = {}")
self.assertEqual(self.context.execute_pending_job(), False)
self.context.eval("Promise.resolve().then(() => {obj.x = 1;})")
self.assertEqual(self.context.execute_pending_job(), True)
self.assertEqual(self.context.eval("obj.x"), 1)
self.assertEqual(self.context.execute_pending_job(), False)
class CallIntoPython(unittest.TestCase):
def setUp(self):
self.context = quickjs.Context()
def test_make_function(self):
self.context.add_callable("f", lambda x: x + 2)
self.assertEqual(self.context.eval("f(40)"), 42)
def test_make_two_functions(self):
for i in range(10):
self.context.add_callable("f", lambda x: i + x + 2)
self.context.add_callable("g", lambda x: i + x + 40)
f = self.context.get("f")
g = self.context.get("g")
self.assertEqual(f(40) - i, 42)
self.assertEqual(g(2) - i, 42)
self.assertEqual(self.context.eval("((f, a) => f(a))")(f, 40) - i, 42)
def test_make_function_call_from_js(self):
self.context.add_callable("f", lambda x: x + 2)
g = self.context.eval("""(
function() {
return f(20) + 20;
}
)""")
self.assertEqual(g(), 42)
def test_python_function_raises(self):
def error(a):
raise ValueError("A")
self.context.add_callable("error", error)
with self.assertRaisesRegex(quickjs.JSException, "Python call failed"):
self.context.eval("error(0)")
def test_make_function_two_args(self):
def concat(a, b):
return a + b
self.context.add_callable("concat", concat)
result = self.context.eval("concat(40, 2)")
self.assertEqual(result, 42)
concat = self.context.get("concat")
result = self.context.eval("((f, a, b) => 22 + f(a, b))")(concat, 10, 10)
self.assertEqual(result, 42)
def test_make_function_two_string_args(self):
"""Without the JS_DupValue in js_c_function, this test crashes."""
def concat(a, b):
return a + "-" + b
self.context.add_callable("concat", concat)
concat = self.context.get("concat")
result = concat("aaa", "bbb")
self.assertEqual(result, "aaa-bbb")
def test_can_eval_in_same_context(self):
self.context.add_callable("f", lambda: 40 + self.context.eval("1 + 1"))
self.assertEqual(self.context.eval("f()"), 42)
def test_can_call_in_same_context(self):
inner = self.context.eval("(function() { return 42; })")
self.context.add_callable("f", lambda: inner())
self.assertEqual(self.context.eval("f()"), 42)
def test_invalid_argument(self):
self.context.add_callable("p", lambda: 42)
self.assertEqual(self.context.eval("p()"), 42)
with self.assertRaisesRegex(quickjs.JSException, "Python call failed"):
self.context.eval("p(1)")
with self.assertRaisesRegex(quickjs.JSException, "Python call failed"):
self.context.eval("p({})")
def test_time_limit_disallowed(self):
self.context.add_callable("f", lambda x: x + 2)
self.context.set_time_limit(1000)
with self.assertRaises(quickjs.JSException):
self.context.eval("f(40)")
def test_conversion_failure_does_not_raise_system_error(self):
# https://github.com/PetterS/quickjs/issues/38
def test_list():
return [1, 2, 3]
self.context.add_callable("test_list", test_list)
with self.assertRaises(quickjs.JSException):
# With incorrect error handling, this (safely) made Python raise a SystemError
# instead of a JS exception.
self.context.eval("test_list()")
class Object(unittest.TestCase):
def setUp(self):
self.context = quickjs.Context()
def test_function_is_object(self):
f = self.context.eval("""
a = function(x) {
return 40 + x;
}
""")
self.assertIsInstance(f, quickjs.Object)
def test_function_call_int(self):
f = self.context.eval("""
f = function(x) {
return 40 + x;
}
""")
self.assertEqual(f(2), 42)
def test_function_call_int_two_args(self):
f = self.context.eval("""
f = function(x, y) {
return 40 + x + y;
}
""")
self.assertEqual(f(3, -1), 42)
def test_function_call_many_times(self):
n = 1000
f = self.context.eval("""
f = function(x, y) {
return x + y;
}
""")
s = 0
for i in range(n):
s += f(1, 1)
self.assertEqual(s, 2 * n)
def test_function_call_str(self):
f = self.context.eval("""
f = function(a) {
return a + " hej";
}
""")
self.assertEqual(f("1"), "1 hej")
def test_function_call_str_three_args(self):
f = self.context.eval("""
f = function(a, b, c) {
return a + " hej " + b + " ho " + c;
}
""")
self.assertEqual(f("1", "2", "3"), "1 hej 2 ho 3")
def test_function_call_object(self):
d = self.context.eval("d = {data: 42};")
f = self.context.eval("""
f = function(d) {
return d.data;
}
""")
self.assertEqual(f(d), 42)
# Try again to make sure refcounting works.
self.assertEqual(f(d), 42)
self.assertEqual(f(d), 42)
def test_function_call_unsupported_arg(self):
f = self.context.eval("""
f = function(x) {
return 40 + x;
}
""")
with self.assertRaisesRegex(TypeError, "Unsupported type"):
self.assertEqual(f({}), 42)
def test_json(self):
d = self.context.eval("d = {data: 42};")
self.assertEqual(json.loads(d.json()), {"data": 42})
def test_call_nonfunction(self):
d = self.context.eval("({data: 42})")
with self.assertRaisesRegex(quickjs.JSException, "TypeError: not a function"):
d(1)
def test_wrong_context(self):
context1 = quickjs.Context()
context2 = quickjs.Context()
f = context1.eval("(function(x) { return x.a; })")
d = context2.eval("({a: 1})")
with self.assertRaisesRegex(ValueError, "Can not mix JS objects from different contexts."):
f(d)
def test_get(self):
self.context.eval("a = {x: 42, y: 'foo'};")
a = self.context.get_global().get("a")
self.assertEqual(a.get("x"), 42)
self.assertEqual(a.get("y"), "foo")
self.assertEqual(a.get("z"), None)
def test_set(self):
self.context.eval("a = {x: 'overridden'}")
a = self.context.get_global().get("a")
a.set("x", 42)
a.set("y", "foo")
self.assertTrue(self.context.eval("a.x == 42"))
self.assertTrue(self.context.eval("a.y == 'foo'"))
def test_make_function(self):
print(11)
self.context.get_global().set("f", lambda x: x + 2)
self.assertEqual(self.context.eval("f(40)"), 42)
class FunctionTest(unittest.TestCase):
def test_adder(self):
f = quickjs.Function(
"adder", """
function adder(x, y) {
return x + y;
}
""")
self.assertEqual(f(1, 1), 2)
self.assertEqual(f(100, 200), 300)
self.assertEqual(f("a", "b"), "ab")
def test_identity(self):
identity = quickjs.Function(
"identity", """
function identity(x) {
return x;
}
""")
for x in [True, [1], {"a": 2}, 1, 1.5, "hej", None]:
self.assertEqual(identity(x), x)
def test_bool(self):
f = quickjs.Function(
"f", """
function f(x) {
return [typeof x ,!x];
}
""")
self.assertEqual(f(False), ["boolean", True])
self.assertEqual(f(True), ["boolean", False])
def test_empty(self):
f = quickjs.Function("f", "function f() { }")
self.assertEqual(f(), None)
def test_lists(self):
f = quickjs.Function(
"f", """
function f(arr) {
const result = [];
arr.forEach(function(elem) {
result.push(elem + 42);
});
return result;
}""")
self.assertEqual(f([0, 1, 2]), [42, 43, 44])
def test_dict(self):
f = quickjs.Function(
"f", """
function f(obj) {
return obj.data;
}""")
self.assertEqual(f({"data": {"value": 42}}), {"value": 42})
def test_time_limit(self):
f = quickjs.Function(
"f", """
function f() {
let arr = [];
for (let i = 0; i < 100000; ++i) {
arr.push(i);
}
return arr;
}
""")
f()
f.set_time_limit(0)
with self.assertRaisesRegex(quickjs.JSException, "InternalError: interrupted"):
f()
f.set_time_limit(-1)
f()
def test_garbage_collection(self):
f = quickjs.Function(
"f", """
function f() {
let a = {};
let b = {};
a.b = b;
b.a = a;
a.i = 42;
return a.i;
}
""")
initial_count = f.memory()["obj_count"]
for i in range(10):
prev_count = f.memory()["obj_count"]
self.assertEqual(f(run_gc=False), 42)
current_count = f.memory()["obj_count"]
self.assertGreater(current_count, prev_count)
f.gc()
self.assertLessEqual(f.memory()["obj_count"], initial_count)
def test_deep_recursion(self):
f = quickjs.Function(
"f", """
function f(v) {
if (v <= 0) {
return 0;
} else {
return 1 + f(v - 1);
}
}
""")
self.assertEqual(f(100), 100)
limit = 500
with self.assertRaises(quickjs.StackOverflow):
f(limit)
f.set_max_stack_size(2000 * limit)
self.assertEqual(f(limit), limit)
def test_add_callable(self):
f = quickjs.Function(
"f", """
function f() {
return pfunc();
}
""")
f.add_callable("pfunc", lambda: 42)
self.assertEqual(f(), 42)
def test_execute_pending_job(self):
f = quickjs.Function(
"f", """
obj = {x: 0, y: 0};
async function a() {
obj.x = await 1;
}
a();
Promise.resolve().then(() => {obj.y = 1});
function f() {
return obj.x + obj.y;
}
""")
self.assertEqual(f(), 0)
self.assertEqual(f.execute_pending_job(), True)
self.assertEqual(f(), 1)
self.assertEqual(f.execute_pending_job(), True)
self.assertEqual(f(), 2)
self.assertEqual(f.execute_pending_job(), False)
class JavascriptFeatures(unittest.TestCase):
def test_unicode_strings(self):
identity = quickjs.Function(
"identity", """
function identity(x) {
return x;
}
""")
context = quickjs.Context()
for x in ["äpple", "≤≥", "☺"]:
self.assertEqual(identity(x), x)
self.assertEqual(context.eval('(function(){ return "' + x + '";})()'), x)
def test_es2020_optional_chaining(self):
f = quickjs.Function(
"f", """
function f(x) {
return x?.one?.two;
}
""")
self.assertIsNone(f({}))
self.assertIsNone(f({"one": 12}))
self.assertEqual(f({"one": {"two": 42}}), 42)
def test_es2020_null_coalescing(self):
f = quickjs.Function(
"f", """
function f(x) {
return x ?? 42;
}
""")
self.assertEqual(f(""), "")
self.assertEqual(f(0), 0)
self.assertEqual(f(11), 11)
self.assertEqual(f(None), 42)
def test_symbol_conversion(self):
context = quickjs.Context()
context.eval("a = Symbol();")
context.set("b", context.eval("a"))
self.assertTrue(context.eval("a === b"))
def test_large_python_integers_to_quickjs(self):
context = quickjs.Context()
# Without a careful implementation, this made Python raise a SystemError/OverflowError.
context.set("v", 10**25)
# There is precision loss occurring in JS due to
# the floating point implementation of numbers.
self.assertTrue(context.eval("v == 1e25"))
def test_bigint(self):
context = quickjs.Context()
self.assertEqual(context.eval(f"BigInt('{10**100}')"), 10**100)
self.assertEqual(context.eval(f"BigInt('{-10**100}')"), -10**100)
class Threads(unittest.TestCase):
def setUp(self):
self.context = quickjs.Context()
self.executor = concurrent.futures.ThreadPoolExecutor()
def tearDown(self):
self.executor.shutdown()
def test_concurrent(self):
"""Demonstrates that the execution will crash unless the function executes on the same
thread every time.
If the executor in Function is not present, this test will fail.
"""
data = list(range(1000))
jssum = quickjs.Function(
"sum", """
function sum(data) {
return data.reduce((a, b) => a + b, 0)
}
""")
futures = [self.executor.submit(jssum, data) for _ in range(10)]
expected = sum(data)
for future in concurrent.futures.as_completed(futures):
self.assertEqual(future.result(), expected)
def test_concurrent_own_executor(self):
data = list(range(1000))
jssum1 = quickjs.Function("sum",
"""
function sum(data) {
return data.reduce((a, b) => a + b, 0)
}
""",
own_executor=True)
jssum2 = quickjs.Function("sum",
"""
function sum(data) {
return data.reduce((a, b) => a + b, 0)
}
""",
own_executor=True)
futures = [self.executor.submit(f, data) for _ in range(10) for f in (jssum1, jssum2)]
expected = sum(data)
for future in concurrent.futures.as_completed(futures):
self.assertEqual(future.result(), expected)
class QJS(object):
def __init__(self):
self.interp = quickjs.Context()
self.interp.eval('var foo = "bar";')
class QuickJSContextInClass(unittest.TestCase):
def test_github_issue_7(self):
# This used to give stack overflow internal error, due to how QuickJS calculates stack
# frames. Passes with the 2021-03-27 release.
#
# TODO: Use the new JS_UpdateStackTop function in order to better handle stacks.
qjs = QJS()
self.assertEqual(qjs.interp.eval('2+2'), 4)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File : quickjs_py交互.py
# Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
# Date : 2022/10/12
from quickjs import Context, Object as QuickJSObject
import json
from pprint import pp
from uuid import UUID
from datetime import date, datetime
# https://github.com/PetterS/quickjs/pull/82 py交互扩展
# print(QuickJSObject)
# QuickJSObject.set('a',1)
# print(Context.get_global())
# exit()
class JS:
interp = None
# Store global variables here. Reference from javascript by path
_globals = None
# Used for generating unique ids in Context namespace
_incr = 0
# I cache the values passed from python to js. Otherwise, we create new representation
# objects each time a value is referenced.
_cache = None
def __init__(self):
self.interp = Context()
self._globals = {}
self._cache = {}
# Install js proxy logic
self.interp.add_callable("proxy_get", self.proxy_get)
self.interp.add_callable("proxy_set", self.proxy_set)
self.interp.eval("""
var handler = {
get(target, property) {
rv = proxy_get(target.path, property)
if (typeof rv == 'string' && rv.substr(0, 5) == 'eval:') {
eval(rv.substr(5));
return eval(rv.substr(5));
}
return rv
},
set(target, property, value) {
return proxy_set(target.path, property, value)
}
}
var mk_proxy = function(path) {
return new Proxy({path: path}, handler);
}
""")
def set(self, **kwargs):
for (k, v) in kwargs.items():
self.interp.set(k, v)
def __call__(self, s):
return self.interp.eval(s)
# -----------------------------------------------------------------
def to_non_proxied(self, v):
# returns True/False and a value if the value can be represented
# by a Javascript type (not proxied)
if v in [None, True, False]:
return True, v
if type(v) in [QuickJSObject, str, int, float]:
return True, v
if type(v) in [UUID]:
return True, str(v)
return False, None
def to_eval_str(self, v, path=None):
# The value will be produced via eval if it is a string starting with eval:
# Cache results
if id(v) and id(v) in self._cache:
return self._cache[id(v)]
# If the value is a list, create a list of return values. Problem is
# that these have no path in the self._globals dict. They will have to
# be duplicated if they are objects.
# BUG here - every reference to the list, create another copy - need to cache
if type(v) == list:
rv = []
for v1 in v:
can_non_proxy, non_proxied = self.to_non_proxied(v1)
if can_non_proxy:
self._incr += 1
self.interp.set("_lv%s" % self._incr, v1)
rv.append("_lv%s" % self._incr)
else:
rv.append(self.to_eval_str(v1))
rv = "[" + ",".join(rv) + "]"
self._cache[id(v)] = rv
return rv
if type(v) == date:
rv = "new Date(%s, %s, %s)" % (v.year, v.month - 1, v.day)
self._cache[id(v)] = rv
return rv
if type(v) == datetime:
rv = "new Date('%s')" % v.isoformat()
self._cache[id(v)] = rv
return rv
# this creates a function, which can never be garbage collected
if callable(v):
self._incr += 1
gname = "_fn%s" % self._incr
self.interp.add_callable(gname, v)
rv = "%s" % gname
self._cache[id(v)] = rv
return rv
# Anonymous variables are created by values inside lists
if path is None:
self._incr += 1
path = "_anon%s" % self._incr
self._globals[path] = v
# I need to do this for objects and try getattr
if type(v) == dict:
rv = "mk_proxy('%s')" % path
self._cache[id(v)] = rv
return rv
# Should be a user defined object to get here. Proxy it.
rv = "mk_proxy('%s')" % path
self._cache[id(v)] = rv
return rv
# -----------------------------------------------------------------
# Proxy Callback Points
def proxy_variable(self, **kwargs):
for (k, v) in kwargs.items():
self._globals[k] = v
self.interp.set(k, None)
js("""%s = mk_proxy("%s");""" % (k, k))
def eval_path(self, path):
parts = path.split(".")
root = self._globals
for part in parts:
root = root[part]
return root
def proxy_get(self, path, property):
# print(path, property)
root = self.eval_path(path)
try:
rv = root.get(property, None)
except:
# Object
rv = getattr(root, property)
# print(path, property, rv)
can_non_proxy, non_proxied = self.to_non_proxied(rv)
if can_non_proxy:
return rv
new_path = path + "." + property
estr = self.to_eval_str(rv, path=new_path)
# print("eval:" + estr)
return "eval:" + estr
def proxy_set(self, path, property, value):
# print(path, property, value)
root = self.eval_path(path)
root[property] = value
if __name__ == '__main__':
# Example access class attributes
class example:
a = "I am a"
a1 = 111
def fn(self, a='not set'):
print("fn() called, a = ", a)
# Example access dict
l = {
"a": 1,
"fn": lambda: "XXXX",
"p1": None,
"p2": {
"p3": "PPP333"
},
"p4": ["A", 4, None, example()],
"p5": example()
}
js = JS()
# Standard Variables
js.set(v1="Set via python")
print("v1 = ", js("v1"))
assert (js("v1") == "Set via python")
js.set(v2=None)
print("v2 = ", js("v2"))
assert (js("v2") is None)
js.proxy_variable(l=l)
# null
print("p1 = ", js("l.p1"))
assert (l['p1'] == js("l.p1"))
# Access dict values
print("l.a = ", js("l.a"))
assert (l['a'] == js("l.a"))
js("l.b = 4")
print("l.b = ", js("l.b"))
assert (l['b'] == 4)
print("fn() = ", js("l.fn()"))
# Undefined attribute
print("l.undef = ", js("l.undef"))
# Nested dict
print("l.p2.p3 = ", js("l.p2.p3"))
assert (l['p2']['p3'] == js("l.p2.p3"))
# Dict assigned from JS - Need to use .json() to unwrap in Python
js("l.c = {d: 4}")
print("l.c = ", js("l.c"))
print("l.c.d = ", js("l.c.d"))
print("l.c = ", l['c'].json())
# List
print("l.p4[1] =", js("l.p4[1]"))
assert (js("l.p4[1]") == l['p4'][1])
print("calling l.p4[3].fn('called')")
js("l.p4[3].fn('called')")
# THIS FAILS - p4 was copied and the original variable is never referenced.
js("l.p4.push('added')")
print("l.p4 = ", l['p4'])
# Python Object accesss
print("l.p5 =", js("l.p5"))
print("l.p5.a1 =", js("l.p5.a1"))
assert (l['p5'].a1 == js("l.p5.a1"))
print("calling l.p5.fn(444)")
js("l.p5.fn(444)")
# Print the global variables - will see anonymous variables
pp(js._globals)
\ No newline at end of file
......@@ -46,7 +46,9 @@
[golang最好的js引擎-otto](https://github.com/robertkrimen/otto)
[dockerfile教程](https://blog.csdn.net/qq_46158060/article/details/125718218)
[获取本地设备信息](https://blog.csdn.net/cui_yonghua/article/details/125508991)
[获取本地设备信息](https://m.jb51.net/article/140716.htm)
[获取本地设备信息](https://m.jb51.net/article/140716.htm)
###### 2022/10/12
- [X] js模式2动态配置链接支持多种壳子
###### 2022/10/11
- [X] 增加自建解析加密示例
- [X] 发布3.9.0镜像,重大升级,优化js装载速度
......
......@@ -19,8 +19,8 @@
"key":"dr_{{ rule.name }}",
"name":"{{ rule.name }}(drpy)",
"type":3,
"api":"{{ host }}/libs/drpy.min.js",
#"api":"{{ host }}/libs/drpy.js",
"api":{% if ISTVB %}"{{ host }}/libs/drpy.ym.js"{% else %}"{{ host }}/libs/drpy.min.js"{% endif %},
#"api":"{{ host }}/libs/drpy.js",
"searchable": {{ rule.searchable }},
"quickSearch": {{ rule.quickSearch }},
"filterable": {{ rule.filterable }},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册