JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
js_std_add_helpers(ctx, 0, NULL);
const char *scripts = "console.log('hello quickjs')";
JS_Eval(ctx, scripts, strlen(scripts), "main", 0);
JS_Eval 方法就是执行 JS 脚本的入口方法。函数原型如下:
JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, const char *filename, int eval_flags){
return JS_EvalThis(ctx, ctx->global_obj, input, input_len, filename, eval_flags);
}
JS_EvalThis函数原型如下:
JSValue JS_EvalThis(JSContext *ctx, JSValueConst this_obj, const char *input,
size_t input_len, const char *filename, int eval_flags){
int eval_type = eval_flags & JS_EVAL_TYPE_MASK;
JSValue ret;
assert(eval_type == JS_EVAL_TYPE_GLOBAL ||
eval_type == JS_EVAL_TYPE_MODULE);
ret = JS_EvalInternal(ctx, this_obj, input, input_len, filename,
eval_flags, -1);
return ret;
}
参数解释:
eval_flags
表示eval的类型,JS_EVAL_TYPE_MASK定义如下:
/* JS_Eval() flags */
#define JS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */
#define JS_EVAL_TYPE_MODULE (1 << 0) /* module code */
#define JS_EVAL_TYPE_DIRECT (2 << 0) /* direct call (internal use) */
#define JS_EVAL_TYPE_INDIRECT (3 << 0) /* indirect call (internal use) */
#define JS_EVAL_TYPE_MASK (3 << 0)
eval的类型有四种:
JS_EvalInternal函数原型如下:
/* the indirection is needed to make 'eval' optional */
static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
const char *input, size_t input_len,
const char *filename, int flags, int scope_idx)
{
if (unlikely(!ctx->eval_internal)) {
return JS_ThrowTypeError(ctx, "eval is not supported");
}
return ctx->eval_internal(ctx, this_obj, input, input_len, filename,
flags, scope_idx);
}
在初始化上下文环境时已经通过JS_AddIntrinsicEval函数将eval_internal设定为了__JS_EvalInternal,具体函数定义如下:
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
const char *input, size_t input_len,
const char *filename, int flags, int scope_idx)
{
JSParseState s1, *s = &s1;
int err, js_mode, eval_type;
JSValue fun_obj, ret_val;
JSStackFrame *sf;
JSVarRef **var_refs;
JSFunctionBytecode *b;
JSFunctionDef *fd;
JSModuleDef *m;
//解析1
js_parse_init(ctx, s, input, input_len, filename);
skip_shebang(s);
//解析2
eval_type = flags & JS_EVAL_TYPE_MASK;
m = NULL;
if (eval_type == JS_EVAL_TYPE_DIRECT) {
JSObject *p;
sf = ctx->rt->current_stack_frame;
assert(sf != NULL);
assert(JS_VALUE_GET_TAG(sf->cur_func) == JS_TAG_OBJECT);
p = JS_VALUE_GET_OBJ(sf->cur_func);
assert(js_class_has_bytecode(p->class_id));
b = p->u.func.function_bytecode;
var_refs = p->u.func.var_refs;
js_mode = b->js_mode;
} else {
sf = NULL;
b = NULL;
var_refs = NULL;
js_mode = 0;
if (flags & JS_EVAL_FLAG_STRICT)
js_mode |= JS_MODE_STRICT;
if (flags & JS_EVAL_FLAG_STRIP)
js_mode |= JS_MODE_STRIP;
if (eval_type == JS_EVAL_TYPE_MODULE) {
JSAtom module_name = JS_NewAtom(ctx, filename);
if (module_name == JS_ATOM_NULL)
return JS_EXCEPTION;
m = js_new_module_def(ctx, module_name);
if (!m)
return JS_EXCEPTION;
js_mode |= JS_MODE_STRICT;
}
}
//解析3,补充1
fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1);
if (!fd)
goto fail1;
s->cur_func = fd;
fd->eval_type = eval_type;
fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT);
fd->backtrace_barrier = ((flags & JS_EVAL_FLAG_BACKTRACE_BARRIER) != 0);
if (eval_type == JS_EVAL_TYPE_DIRECT) {
fd->new_target_allowed = b->new_target_allowed;
fd->super_call_allowed = b->super_call_allowed;
fd->super_allowed = b->super_allowed;
fd->arguments_allowed = b->arguments_allowed;
} else {
fd->new_target_allowed = FALSE;
fd->super_call_allowed = FALSE;
fd->super_allowed = FALSE;
fd->arguments_allowed = TRUE;
}
fd->js_mode = js_mode;
fd->func_name = JS_DupAtom(ctx, JS_ATOM__eval_);
if (b) {
if (add_closure_variables(ctx, fd, b, scope_idx))
goto fail;
}
fd->module = m;
s->is_module = (m != NULL);
s->allow_html_comments = !s->is_module;
push_scope(s); /* body scope */
fd->body_scope = fd->scope_level;
err = js_parse_program(s);
if (err) {
fail:
free_token(s, &s->token);
js_free_function_def(ctx, fd);
goto fail1;
}
/* create the function object and all the enclosed functions */
fun_obj = js_create_function(ctx, fd);
if (JS_IsException(fun_obj))
goto fail1;
/* Could add a flag to avoid resolution if necessary */
if (m) {
m->func_obj = fun_obj;
if (js_resolve_module(ctx, m) < 0)
goto fail1;
fun_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
}
if (flags & JS_EVAL_FLAG_COMPILE_ONLY) {
ret_val = fun_obj;
} else {
ret_val = JS_EvalFunctionInternal(ctx, fun_obj, this_obj, var_refs, sf);
}
return ret_val;
fail1:
/* XXX: should free all the unresolved dependencies */
if (m)
js_free_module_def(ctx, m);
return JS_EXCEPTION;
}
实际起作用的是 __JS_EvalInternal 函数,内部会先声明整个脚本解析涉及的内容,比如 JSParseState s、函数对象和返回值 fun_obj、栈帧 sf、变量指针 var_refs、函数字节码 b、函数 fd、模块 m 等。通过函数 js_parse_init 来设置初始化 JSParseState。有上下文 ctx、文件名 filename、根据输入 input 字符串长度设置缓存大小和初始化 token 等。
js_parse_init 函数执行完后会过滤 Shebang,Shebang 一般是会在类 Unix 脚本的第一行,规则是开头两个字符是#!,作用是告诉系统希望用什么解释器执行脚本,比如#!/bin/bash 表示希望用 bash 来执行脚本。在 QuickJS 里显然 Shebang 起不了什么用,因此通过 skip_shebang 函数过滤掉。
关于flag的四个宏定义:
#define JS_EVAL_FLAG_STRICT (1 << 3) //10XX,表示force 'strict' mode
#define JS_EVAL_FLAG_STRIP (1 << 4) //100XX,表示force 'strip' mode
#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5) //1000XX,表示编译但不运行,结果是an object with a JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag.可以通过JS_EvalFunction()执行。
/* in the Error() backtraces */
#define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6) //10000xx,表示在Error()回溯过程中don't include the stack frames before this eval
这里js_new_function_def函数创建了一个顶层的函数定义节点,其第二个参数(父函数)设置为 NULL,后面再解析出来的函数都会成为其子函数。js_new_function_def 会返回一个初始化的 JSFunctionDef。(补充2)
js_new_function_def函数定义:
static JSFunctionDef *js_new_function_def(JSContext *ctx,
JSFunctionDef *parent,
BOOL is_eval,
BOOL is_func_expr,
const char *filename, int line_num)
{
JSFunctionDef *fd;
fd = js_mallocz(ctx, sizeof(*fd));
if (!fd)
return NULL;
fd->ctx = ctx;
init_list_head(&fd->child_list);
/* insert in parent list */
fd->parent = parent;
fd->parent_cpool_idx = -1;
if (parent) {
list_add_tail(&fd->link, &parent->child_list);
fd->js_mode = parent->js_mode;
fd->parent_scope_level = parent->scope_level;
}
fd->is_eval = is_eval;
fd->is_func_expr = is_func_expr;
js_dbuf_init(ctx, &fd->byte_code);
fd->last_opcode_pos = -1;
fd->func_name = JS_ATOM_NULL;
fd->var_object_idx = -1;
fd->arg_var_object_idx = -1;
fd->arguments_var_idx = -1;
fd->arguments_arg_idx = -1;
fd->func_var_idx = -1;
fd->eval_ret_idx = -1;
fd->this_var_idx = -1;
fd->new_target_var_idx = -1;
fd->this_active_func_var_idx = -1;
fd->home_object_var_idx = -1;
/* XXX: should distinguish arg, var and var object and body scopes */
fd->scopes = fd->def_scope_array;
fd->scope_size = countof(fd->def_scope_array);
fd->scope_count = 1;
fd->scopes[0].first = -1;
fd->scopes[0].parent = -1;
fd->scope_level = 0; /* 0: var/arg scope */
fd->scope_first = -1;
fd->body_scope = -1;
fd->filename = JS_NewAtom(ctx, filename);
fd->line_num = line_num;
js_dbuf_init(ctx, &fd->pc2line);
//fd->pc2line_last_line_num = line_num;
//fd->pc2line_last_pc = 0;
fd->last_opcode_line_num = line_num;
return fd;
}
结构体 JSFunctionDef 定义
typedef struct JSFunctionDef {
JSContext *ctx;
struct JSFunctionDef *parent;
//当前函数在parent常量池中的索引,or -1 if none
int parent_cpool_idx;
//scope level in parent at point of definition
int parent_scope_level;
//子函数列表,为JSFunctionDef.link的列表
struct list_head child_list;
struct list_head link;
//如果为true,那么表示当前函数代码是在eval中调用的
BOOL is_eval;
int eval_type; /* only valid if is_eval = TRUE */
BOOL is_global_var; /* TRUE if variables are not defined locally:
eval global, eval module or non strict eval */
BOOL is_func_expr; /* TRUE if function expression */
//表示当前函数是否有home object,每个函数都有一个slot用来存函数的home object,
BOOL has_home_object; /* TRUE if the home object is available */
//当前函数存在prototype时返回true,class都有prototype
BOOL has_prototype;
BOOL has_simple_parameter_list;
//当前函数是否有参数表达式,如果有的话创建参数作用域
BOOL has_parameter_expressions;
BOOL has_use_strict; /* to reject directive in special cases */
BOOL has_eval_call; /* true if the function contains a call to eval() */
BOOL has_arguments_binding; /* true if the 'arguments' binding is
available in the function */
BOOL has_this_binding; /* true if the 'this' and new.target binding are
available in the function */
BOOL new_target_allowed; /* true if the 'new.target' does not
throw a syntax error */
BOOL super_call_allowed; /* true if super() is allowed */
BOOL super_allowed; /* true if super. or super[] is allowed */
BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */
BOOL is_derived_class_constructor;
BOOL in_function_body;
BOOL backtrace_barrier;
JSFunctionKindEnum func_kind : 8;
JSParseFunctionEnum func_type : 8;
uint8_t js_mode; /* bitmap of JS_MODE_x */
JSAtom func_name; /* JS_ATOM_NULL if no name */
JSVarDef *vars;
int var_size; /* allocated size for vars[] */
int var_count;
JSVarDef *args;
int arg_size; /* allocated size for args[] */
int arg_count; /* number of arguments */
int defined_arg_count;
int var_object_idx; /* -1 if none */
int arg_var_object_idx; /* -1 if none (var object for the argument scope) */
int arguments_var_idx; /* -1 if none */
int arguments_arg_idx; /* argument variable definition in argument scope,
-1 if none */
int func_var_idx; /* variable containing the current function (-1
if none, only used if is_func_expr is true) */
int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */
int this_var_idx; /* variable containg the 'this' value, -1 if none */
int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */
int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */
int home_object_var_idx;
BOOL need_home_object;
int scope_level; /* index into fd->scopes if the current lexical scope */
int scope_first; /* index into vd->vars of first lexically scoped variable */
int scope_size; /* allocated size of fd->scopes array */
int scope_count; /* number of entries used in the fd->scopes array */
JSVarScope *scopes;
JSVarScope def_scope_array[4];
int body_scope; /* scope of the body of the function or eval */
int global_var_count;
int global_var_size;
JSGlobalVar *global_vars;
DynBuf byte_code;
int last_opcode_pos; /* -1 if no last opcode */
int last_opcode_line_num;
BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */
LabelSlot *label_slots;
int label_size; /* allocated size for label_slots[] */
int label_count;
BlockEnv *top_break; /* break/continue label stack */
/* constant pool (strings, functions, numbers) */
JSValue *cpool;
int cpool_count;
int cpool_size;
/* list of variables in the closure */
int closure_var_count;
int closure_var_size;
JSClosureVar *closure_var;
JumpSlot *jump_slots;
int jump_size;
int jump_count;
LineNumberSlot *line_number_slots;
int line_number_size;
int line_number_count;
int line_number_last;
int line_number_last_pc;
/* pc2line table */
JSAtom filename;
int line_num;
DynBuf pc2line;
char *source; /* raw source, utf-8 encoded */
int source_len;
JSModuleDef *module; /* != NULL when parsing a module */
} JSFunctionDef;