/* * QuickJS Javascript Engine * * Copyright (c) 2017-2025 Fabrice Bellard * Copyright (c) 2017-2025 Charlie Gordon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #if defined(__APPLE__) #include #elif defined(__linux__) || defined(__GLIBC__) #include #elif defined(__FreeBSD__) #include #endif #include "cutils.h" #include "list.h" #include "quickjs.h" #include "libregexp.h" #include "libunicode.h" #include "dtoa.h" #define BLOB_IMPLEMENTATION #include "blob.h" #define OPTIMIZE 1 #define SHORT_OPCODES 1 #if defined(EMSCRIPTEN) #define DIRECT_DISPATCH 0 #else #define DIRECT_DISPATCH 1 #endif #if defined(__APPLE__) #define MALLOC_OVERHEAD 0 #else #define MALLOC_OVERHEAD 8 #endif #if !defined(_WIN32) /* define it if printf uses the RNDN rounding mode instead of RNDNA */ #define CONFIG_PRINTF_RNDN #endif #if !defined(EMSCRIPTEN) /* enable stack limitation */ #define CONFIG_STACK_CHECK #endif /* dump object free */ //#define DUMP_FREE //#define DUMP_CLOSURE /* dump the bytecode of the compiled functions: combination of bits 1: dump pass 3 final byte code 2: dump pass 2 code 4: dump pass 1 code 8: dump stdlib functions 16: dump bytecode in hex 32: dump line number table 64: dump compute_stack_size */ //#define DUMP_BYTECODE (1) /* dump the occurence of the automatic GC */ //#define DUMP_GC /* dump objects freed by the garbage collector */ //#define DUMP_GC_FREE /* dump objects leaking when freeing the runtime */ //#define DUMP_LEAKS 1 /* dump memory usage before running the garbage collector */ //#define DUMP_MEM //#define DUMP_OBJECTS /* dump objects in JS_FreeContext */ //#define DUMP_ATOMS /* dump atoms in JS_FreeContext */ //#define DUMP_SHAPES /* dump shapes in JS_FreeContext */ //#define DUMP_READ_OBJECT //#define DUMP_ROPE_REBALANCE /* dump profiling statistics (counters and sampling) when freeing the runtime */ //#define DUMP_PROFILE /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC #ifdef DUMP_PROFILE /* Profiling data structures (defined early since they're embedded in JSRuntime/JSFunctionBytecode) */ /* Per-function profiling counters (cheap, always-on under DUMP_PROFILE) */ typedef struct JSFunctionProfile { uint64_t entry_count; /* how many times this function was called */ uint64_t self_ticks; /* bytecode instructions executed in this function */ } JSFunctionProfile; /* Per-callsite profiling counters */ typedef struct JSCallSite { uint32_t pc_offset; /* bytecode offset of the call instruction */ uint64_t hit_count; /* how many times this callsite was executed */ } JSCallSite; /* Per-property-access-site profiling counters */ typedef struct JSPropSite { uint32_t pc_offset; /* bytecode offset of the get/put instruction */ JSAtom atom; /* property name/atom */ uint64_t hit_count; /* how many times this prop site was executed */ } JSPropSite; /* Forward declarations */ typedef struct JSFunctionBytecode JSFunctionBytecode; typedef struct JSVarRef JSVarRef; typedef struct VMFrame VMFrame; /* Sampling profiler sample */ typedef struct JSProfileSample { JSFunctionBytecode *func; /* function being executed */ uint32_t pc_offset; /* bytecode PC offset */ uint32_t callsite_id; /* callsite id if available */ } JSProfileSample; #define MAX_PROFILE_SAMPLES 100000 /* Runtime-wide profiling state */ typedef struct JSProfileState { /* Sampling profiler */ JSProfileSample *samples; uint32_t sample_count; uint32_t sample_capacity; struct timeval last_sample_time; uint32_t sample_interval_us; /* microseconds between samples (1000-5000) */ } JSProfileState; #endif /* DUMP_PROFILE */ static inline JS_BOOL JS_VALUE_IS_TEXT(JSValue v) { int tag = JS_VALUE_GET_TAG(v); return tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE; } static inline JS_BOOL JS_VALUE_IS_NUMBER(JSValue v) { int tag = JS_VALUE_GET_TAG(v); return tag == JS_TAG_INT || tag == JS_TAG_FLOAT64; } enum { /* classid tag */ /* union usage | properties */ JS_CLASS_OBJECT = 1, /* must be first */ JS_CLASS_ARRAY, /* u.array | length */ JS_CLASS_ERROR, JS_CLASS_NUMBER, /* u.object_data */ JS_CLASS_STRING, /* u.object_data */ JS_CLASS_BOOLEAN, /* u.object_data */ JS_CLASS_SYMBOL, /* u.object_data */ JS_CLASS_C_FUNCTION, /* u.cfunc */ JS_CLASS_BYTECODE_FUNCTION, /* u.func */ JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ JS_CLASS_REGEXP, /* u.regexp */ JS_CLASS_FINALIZATION_REGISTRY, JS_CLASS_BLOB, /* u.opaque (blob *) */ JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ }; typedef enum JSErrorEnum { JS_EVAL_ERROR, JS_RANGE_ERROR, JS_REFERENCE_ERROR, JS_SYNTAX_ERROR, JS_TYPE_ERROR, JS_URI_ERROR, JS_INTERNAL_ERROR, JS_AGGREGATE_ERROR, JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ } JSErrorEnum; /* the variable and scope indexes must fit on 16 bits. The (-1) and ARG_SCOPE_END values are reserved. */ #define JS_MAX_LOCAL_VARS 65534 #define JS_STACK_SIZE_MAX 65534 #define JS_STRING_LEN_MAX ((1 << 30) - 1) /* strings <= this length are not concatenated using ropes. if too small, the rope memory overhead becomes high. */ #define JS_STRING_ROPE_SHORT_LEN 512 /* specific threshold for initial rope use */ #define JS_STRING_ROPE_SHORT2_LEN 8192 /* rope depth at which we rebalance */ #define JS_STRING_ROPE_MAX_DEPTH 60 #define __exception __attribute__((warn_unused_result)) typedef struct JSShape JSShape; typedef struct JSString JSString; typedef struct JSString JSAtomStruct; typedef struct JSObject JSObject; #define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v)) #define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) #define JS_VALUE_GET_STRING_ROPE(v) ((JSStringRope *)JS_VALUE_GET_PTR(v)) typedef enum { JS_GC_PHASE_NONE, JS_GC_PHASE_DECREF, JS_GC_PHASE_REMOVE_CYCLES, } JSGCPhaseEnum; typedef enum OPCodeEnum OPCodeEnum; struct JSRuntime { JSMallocFunctions mf; JSMallocState malloc_state; const char *rt_info; int atom_hash_size; /* power of two */ int atom_count; int atom_size; int atom_count_resize; /* resize hash table at this count */ uint32_t *atom_hash; JSAtomStruct **atom_array; int atom_free_index; /* 0 = none */ int class_count; /* size of class_array */ JSClass *class_array; struct list_head context_list; /* list of JSContext.link */ /* list of JSGCObjectHeader.link. List of allocated GC objects (used by the garbage collector) */ struct list_head gc_obj_list; /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ struct list_head gc_zero_ref_count_list; struct list_head tmp_obj_list; /* used during GC */ JSGCPhaseEnum gc_phase : 8; size_t malloc_gc_threshold; #ifdef DUMP_LEAKS struct list_head string_list; /* list of JSString.link */ #endif /* stack limitation */ uintptr_t stack_size; /* in bytes, 0 if no limit */ uintptr_t stack_top; uintptr_t stack_limit; /* lower stack limit */ JSValue current_exception; /* true if the current exception cannot be catched */ BOOL current_exception_is_uncatchable : 8; /* true if inside an out of memory error, to avoid recursing */ BOOL in_out_of_memory : 8; struct JSStackFrame *current_stack_frame; struct VMFrame *current_vm_frame; /* for trampoline VM */ JSInterruptHandler *interrupt_handler; void *interrupt_opaque; /* see JS_SetStripInfo() */ uint8_t strip_flags; /* Shape hash table */ int shape_hash_bits; int shape_hash_size; int shape_hash_count; /* number of hashed shapes */ JSShape **shape_hash; void *user_opaque; JSContext *js; #ifdef DUMP_PROFILE JSProfileState profile; #endif }; struct JSClass { uint32_t class_id; /* 0 means free entry */ JSAtom class_name; JSClassFinalizer *finalizer; JSClassGCMark *gc_mark; JSClassCall *call; /* pointers for exotic behavior, can be NULL if none are present */ const JSClassExoticMethods *exotic; }; #define JS_MODE_BACKTRACE_BARRIER (1 << 3) /* stop backtrace before this frame */ typedef struct JSStackFrame { struct JSStackFrame *prev_frame; /* NULL if first stack frame */ JSValue cur_func; /* current function, JS_NULL if the frame is detached */ JSValue *arg_buf; /* arguments */ JSValue *var_buf; /* variables */ struct list_head var_ref_list; /* list of JSVarRef.var_ref_link */ const uint8_t *cur_pc; /* only used in bytecode functions : PC of the instruction after the call */ int arg_count; int js_mode; /* not supported for C functions */ } JSStackFrame; /* Heap-allocated VM frame for trampoline execution */ struct VMFrame { struct JSFunctionBytecode *b; /* current function bytecode */ JSContext *ctx; /* execution context / realm */ const uint8_t *pc; /* program counter */ /* Offset-based storage (safe with realloc) */ int value_stack_base; /* base index into ctx->value_stack */ int sp_offset; /* sp offset from base */ int arg_buf_offset; /* arg buffer offset from base (or -1 if aliased) */ int var_buf_offset; /* var buffer offset from base */ /* Continuation info for return */ const uint8_t *ret_pc; /* where to resume in caller */ int ret_sp_offset; /* caller's sp before call (for cleanup) */ int call_argc; /* number of args to clean up */ int call_has_this; /* whether call had this (method call) */ JSValue cur_func; /* current function object */ JSValue this_obj; /* this binding */ JSValue new_target; /* new.target binding */ struct JSVarRef **var_refs; /* closure variable references */ struct list_head var_ref_list; /* list of JSVarRef.var_ref_link */ int arg_count; int js_mode; int stack_size_allocated; /* total size allocated for this frame */ }; /* Execution state returned by vm_execute_frame */ typedef enum { VM_EXEC_NORMAL, /* Continue executing current frame */ VM_EXEC_RETURN, /* Frame returned, pop and resume caller */ VM_EXEC_CALL, /* Need to push new frame for call */ VM_EXEC_EXCEPTION, /* Exception thrown, unwind frames */ } VMExecState; /* Call info for frame push */ typedef struct { JSValue func_obj; JSValue this_obj; JSValue new_target; int argc; JSValue *argv; const uint8_t *ret_pc; int ret_sp_offset; int call_argc; int call_has_this; int is_tail_call; } VMCallInfo; typedef enum { JS_GC_OBJ_TYPE_JS_OBJECT, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, JS_GC_OBJ_TYPE_SHAPE, JS_GC_OBJ_TYPE_VAR_REF, JS_GC_OBJ_TYPE_JS_CONTEXT, } JSGCObjectTypeEnum; /* header for GC objects. GC objects are C data structures with a reference count that can reference other GC objects. JS Objects are a particular type of GC object. */ struct JSGCObjectHeader { int ref_count; /* must come first, 32-bit */ JSGCObjectTypeEnum gc_obj_type : 4; uint8_t mark : 4; /* used by the GC */ uint8_t dummy1; /* not used by the GC */ uint16_t dummy2; /* not used by the GC */ struct list_head link; }; typedef struct JSVarRef { union { JSGCObjectHeader header; /* must come first */ struct { int __gc_ref_count; /* corresponds to header.ref_count */ uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ uint8_t is_detached; }; }; JSValue *pvalue; /* pointer to the value, either on the stack or to 'value' */ union { JSValue value; /* used when is_detached = TRUE */ struct { struct list_head var_ref_link; /* JSStackFrame.var_ref_list list */ }; /* used when is_detached = FALSE */ }; } JSVarRef; typedef enum { JS_AUTOINIT_ID_PROTOTYPE, JS_AUTOINIT_ID_PROP, } JSAutoInitIDEnum; /* must be large enough to have a negligible runtime cost and small enough to call the interrupt callback often. */ #define JS_INTERRUPT_COUNTER_INIT 10000 struct JSContext { JSGCObjectHeader header; /* must come first */ JSRuntime *rt; struct list_head link; uint16_t binary_object_count; int binary_object_size; JSShape *array_shape; /* initial shape for Array objects */ JSValue *class_proto; JSValue function_proto; JSValue function_ctor; JSValue array_ctor; JSValue regexp_ctor; JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; JSValue array_proto_values; JSValue throw_type_error; JSValue eval_obj; JSValue global_obj; /* global object */ JSValue global_var_obj; /* contains the global let/const definitions */ uint64_t random_state; /* when the counter reaches zero, JSRutime.interrupt_handler is called */ int interrupt_counter; /* if NULL, RegExp compilation is not supported */ JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern, JSValueConst flags); /* if NULL, eval is not supported */ JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); void *user_opaque; js_hook trace_hook; int trace_type; void *trace_data; /* Trampoline VM stacks (per actor/context) */ struct VMFrame *frame_stack; /* array of frames */ int frame_stack_top; /* current frame index (-1 = empty) */ int frame_stack_capacity; /* allocated capacity */ JSValue *value_stack; /* array of JSValues for locals/operands */ int value_stack_top; /* current top index */ int value_stack_capacity; /* allocated capacity */ }; typedef union JSFloat64Union { double d; uint64_t u64; uint32_t u32[2]; } JSFloat64Union; enum { JS_ATOM_TYPE_STRING = 1, JS_ATOM_TYPE_SYMBOL, }; typedef enum { JS_ATOM_KIND_STRING, JS_ATOM_KIND_SYMBOL, } JSAtomKindEnum; #define JS_ATOM_HASH_MASK ((1 << 30) - 1) struct JSString { JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len : 31; uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ /* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3, for JS_ATOM_TYPE_PRIVATE: hash = JS_ATOM_HASH_PRIVATE, atom_type = 3 XXX: could change encoding to have one more bit in hash */ uint32_t hash : 30; uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ #ifdef DUMP_LEAKS struct list_head link; /* string list */ #endif union { uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ uint16_t str16[0]; } u; }; /* Extended symbol atom structure with object-key payload */ typedef struct JSAtomSymbol { JSString s; /* base atom struct */ JSValue obj_key; /* JS_UNDEFINED for normal symbols; strong ref for object-key symbols */ } JSAtomSymbol; static inline JSAtomSymbol *js_atom_as_symbol(JSAtomStruct *p) { return (JSAtomSymbol *)p; } typedef struct JSStringRope { JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len; uint8_t is_wide_char; /* 0 = 8 bits, 1 = 16 bits characters */ uint8_t depth; /* max depth of the rope tree */ /* XXX: could reduce memory usage by using a direct pointer with bit 0 to select rope or string */ JSValue left; JSValue right; /* might be the empty string */ } JSStringRope; typedef struct JSClosureVar { uint8_t is_local : 1; uint8_t is_arg : 1; uint8_t is_const : 1; uint8_t is_lexical : 1; uint8_t var_kind : 4; /* see JSVarKindEnum */ /* 8 bits available */ uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the parent function. otherwise: index to a closure variable of the parent function */ JSAtom var_name; } JSClosureVar; #define ARG_SCOPE_INDEX 1 #define ARG_SCOPE_END (-2) typedef struct JSVarScope { int parent; /* index into fd->scopes of the enclosing scope */ int first; /* index into fd->vars of the last variable in this scope */ } JSVarScope; typedef enum { /* XXX: add more variable kinds here instead of using bit fields */ JS_VAR_NORMAL, JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator function declaration */ JS_VAR_CATCH, JS_VAR_FUNCTION_NAME, /* function expression name */ } JSVarKindEnum; /* XXX: could use a different structure in bytecode functions to save memory */ typedef struct JSVarDef { JSAtom var_name; /* index into fd->scopes of this variable lexical scope */ int scope_level; /* during compilation: - if scope_level = 0: scope in which the variable is defined - if scope_level != 0: index into fd->vars of the next variable in the same or enclosing lexical scope in a bytecode function: index into fd->vars of the next variable in the same or enclosing lexical scope */ int scope_next; uint8_t is_const : 1; uint8_t is_lexical : 1; uint8_t is_captured : 1; uint8_t var_kind : 4; /* see JSVarKindEnum */ /* only used during compilation: function pool index for lexical variables with var_kind = JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of the definition of the 'var' variables (they have scope_level = 0) */ int func_pool_idx : 24; /* only used during compilation : index in the constant pool for hoisted function definition */ } JSVarDef; /* for the encoding of the pc2line table */ #define PC2LINE_BASE (-1) #define PC2LINE_RANGE 5 #define PC2LINE_OP_FIRST 1 #define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) typedef struct JSFunctionBytecode { JSGCObjectHeader header; /* must come first */ uint8_t js_mode; uint8_t has_prototype : 1; /* true if a prototype field is necessary */ uint8_t has_simple_parameter_list : 1; uint8_t is_derived_class_constructor : 1; uint8_t func_kind : 2; uint8_t new_target_allowed : 1; uint8_t has_debug : 1; uint8_t read_only_bytecode : 1; uint8_t is_direct_or_indirect_eval : 1; /* used by JS_GetScriptOrModuleName() */ /* XXX: 10 bits available */ uint8_t *byte_code_buf; /* (self pointer) */ int byte_code_len; JSAtom func_name; JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ uint16_t arg_count; uint16_t var_count; uint16_t defined_arg_count; /* for length function property */ uint16_t stack_size; /* maximum stack size */ JSContext *realm; /* function realm */ JSValue *cpool; /* constant pool (self pointer) */ int cpool_count; int closure_var_count; struct { /* debug info, move to separate structure to save memory? */ JSAtom filename; int source_len; int pc2line_len; uint8_t *pc2line_buf; char *source; } debug; #ifdef DUMP_PROFILE /* Profiling data */ JSFunctionProfile profile; JSCallSite *call_sites; uint32_t call_site_count; uint32_t call_site_capacity; JSPropSite *prop_sites; uint32_t prop_site_count; uint32_t prop_site_capacity; #endif /* Inline caches - forward declared below */ struct ICSlot *ic_slots; /* array of IC slots (self pointer) */ uint32_t ic_count; /* number of IC slots */ } JSFunctionBytecode; /* Inline cache (IC) support - defined after JSFunctionBytecode */ typedef enum { IC_NONE = 0, IC_GET_PROP, IC_SET_PROP, IC_CALL, } ic_kind; typedef enum { IC_STATE_UNINIT = 0, IC_STATE_MONO, IC_STATE_POLY, IC_STATE_MEGA, } ic_state; /* Property lookup IC (monomorphic case) */ typedef struct { JSShape *shape; /* expected shape */ uint32_t offset; /* property offset in prop array */ } GetPropIC; typedef struct { JSShape *shape; uint32_t offset; } SetPropIC; /* Call IC (monomorphic case) */ typedef struct { JSObject *func_obj; /* expected function object */ JSFunctionBytecode *b; /* direct pointer to bytecode */ uint8_t is_bytecode_func; /* 1 if bytecode function, 0 if native */ uint8_t expected_argc; /* expected argument count */ } CallIC; /* Unified IC slot with tagged union */ typedef struct ICSlot { uint8_t kind; /* ic_kind */ uint8_t state; /* ic_state */ uint16_t aux; /* auxiliary flags/data */ union { GetPropIC get_prop; SetPropIC set_prop; CallIC call; } u; } ICSlot; typedef struct JSBoundFunction { JSValue func_obj; JSValue this_val; int argc; JSValue argv[0]; } JSBoundFunction; /* Used by js_object_keys and related functions */ typedef enum JSIteratorKindEnum { JS_ITERATOR_KIND_KEY, JS_ITERATOR_KIND_VALUE, JS_ITERATOR_KIND_KEY_AND_VALUE, } JSIteratorKindEnum; typedef struct JSRegExp { JSString *pattern; JSString *bytecode; /* also contains the flags */ } JSRegExp; typedef enum { /* binary operators */ JS_OVOP_ADD, JS_OVOP_SUB, JS_OVOP_MUL, JS_OVOP_DIV, JS_OVOP_MOD, JS_OVOP_POW, JS_OVOP_OR, JS_OVOP_AND, JS_OVOP_XOR, JS_OVOP_SHL, JS_OVOP_SAR, JS_OVOP_SHR, JS_OVOP_EQ, JS_OVOP_LESS, JS_OVOP_BINARY_COUNT, /* unary operators */ JS_OVOP_POS = JS_OVOP_BINARY_COUNT, JS_OVOP_NEG, JS_OVOP_INC, JS_OVOP_DEC, JS_OVOP_NOT, JS_OVOP_COUNT, } JSOverloadableOperatorEnum; typedef struct { uint32_t operator_index; JSObject *ops[JS_OVOP_BINARY_COUNT]; /* self operators */ } JSBinaryOperatorDefEntry; typedef struct { int count; JSBinaryOperatorDefEntry *tab; } JSBinaryOperatorDef; typedef struct { uint32_t operator_counter; BOOL is_primitive; /* OperatorSet for a primitive type */ /* NULL if no operator is defined */ JSObject *self_ops[JS_OVOP_COUNT]; /* self operators */ JSBinaryOperatorDef left; JSBinaryOperatorDef right; } JSOperatorSetData; typedef struct JSProperty { union { JSValue value; /* JS_PROP_NORMAL */ struct { /* JS_PROP_GETSET */ JSObject *getter; /* NULL if undefined */ JSObject *setter; /* NULL if undefined */ } getset; JSVarRef *var_ref; /* JS_PROP_VARREF */ struct { /* JS_PROP_AUTOINIT */ /* in order to use only 2 pointers, we compress the realm and the init function pointer */ uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) in the 2 low bits */ void *opaque; } init; } u; } JSProperty; #define JS_PROP_INITIAL_SIZE 2 #define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ #define JS_ARRAY_INITIAL_SIZE 2 typedef struct JSShapeProperty { uint32_t hash_next : 26; /* 0 if last in list */ uint32_t flags : 6; /* JS_PROP_XXX */ JSAtom atom; /* JS_ATOM_NULL = free property entry */ } JSShapeProperty; struct JSShape { /* hash table of size hash_mask + 1 before the start of the structure (see prop_hash_end()). */ JSGCObjectHeader header; /* true if the shape is inserted in the shape hash table. If not, JSShape.hash is not valid */ uint8_t is_hashed; /* If true, the shape may have small array index properties 'n' with 0 <= n <= 2^31-1. If false, the shape is guaranteed not to have small array index properties */ uint8_t has_small_array_index; uint32_t hash; /* current hash value */ uint32_t prop_hash_mask; int prop_size; /* allocated properties */ int prop_count; /* include deleted properties */ int deleted_prop_count; JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ JSObject *proto; JSShapeProperty prop[0]; /* prop_size elements */ }; struct JSObject { union { JSGCObjectHeader header; struct { int __gc_ref_count; /* corresponds to header.ref_count */ uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ uint8_t extensible : 1; uint8_t free_mark : 1; /* only used when freeing objects with cycles */ uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY and typed arrays) */ uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */ uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ uint16_t class_id; /* see JS_CLASS_x */ }; }; JSShape *shape; /* prototype and property names + flag */ JSProperty *prop; /* array of properties */ JSAtom object_key_atom; /* cached atom index for object-as-key (non-owning hint) */ union { void *opaque; struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ struct JSFunctionBytecode *function_bytecode; JSVarRef **var_refs; } func; struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ JSContext *realm; JSCFunctionType c_function; uint8_t length; uint8_t cproto; int16_t magic; } cfunc; /* array part for fast arrays and typed arrays */ struct { /* JS_CLASS_ARRAY, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ union { uint32_t size; /* JS_CLASS_ARRAY */ } u1; union { JSValue *values; /* JS_CLASS_ARRAY */ } u; uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ } array; /* 12/20 bytes */ JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ } u; }; enum { __JS_ATOM_NULL = JS_ATOM_NULL, #define DEF(name, str) JS_ATOM_ ## name, #include "quickjs-atom.h" #undef DEF JS_ATOM_END, }; #define JS_ATOM_LAST_KEYWORD JS_ATOM_super #define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield static const char js_atom_init[] = #define DEF(name, str) str "\0" #include "quickjs-atom.h" #undef DEF ; typedef enum OPCodeFormat { #define FMT(f) OP_FMT_ ## f, #define DEF(id, size, n_pop, n_push, f) #include "quickjs-opcode.h" #undef DEF #undef FMT } OPCodeFormat; enum OPCodeEnum { #define FMT(f) #define DEF(id, size, n_pop, n_push, f) OP_ ## id, #define def(id, size, n_pop, n_push, f) #include "quickjs-opcode.h" #undef def #undef DEF #undef FMT OP_COUNT, /* excluding temporary opcodes */ /* temporary opcodes : overlap with the short opcodes */ OP_TEMP_START = OP_nop + 1, OP___dummy = OP_TEMP_START - 1, #define FMT(f) #define DEF(id, size, n_pop, n_push, f) #define def(id, size, n_pop, n_push, f) OP_ ## id, #include "quickjs-opcode.h" #undef def #undef DEF #undef FMT OP_TEMP_END, }; static int JS_InitAtoms(JSRuntime *rt); static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, int atom_type); static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags); static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags); static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags); static JSValue JS_CallInternal_OLD(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags); static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, int argc, JSValueConst *argv); static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, int argc, JSValueConst *argv); static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, JSValue val, BOOL is_array_ctor); static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, JSValueConst val, int flags, int scope_idx); JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val); static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val); static __maybe_unused void JS_DumpShapes(JSRuntime *rt); static void js_dump_value_write(void *opaque, const char *buf, size_t len); static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); static void js_array_finalizer(JSRuntime *rt, JSValue val); static void js_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_object_data_finalizer(JSRuntime *rt, JSValue val); static void js_object_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_c_function_finalizer(JSRuntime *rt, JSValue val); static void js_c_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val); static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_bound_function_finalizer(JSRuntime *rt, JSValue val); static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_regexp_finalizer(JSRuntime *rt, JSValue val); #define HINT_STRING 0 #define HINT_NUMBER 1 #define HINT_NONE 2 #define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); static int JS_ToBoolFree(JSContext *ctx, JSValue val); static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, JSValueConst flags); static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, JSValue pattern, JSValue bc); static void gc_decref(JSRuntime *rt); static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def, JSAtom name); typedef enum JSStrictEqModeEnum { JS_EQ_STRICT, JS_EQ_SAME_VALUE, JS_EQ_SAME_VALUE_ZERO, } JSStrictEqModeEnum; static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, JSStrictEqModeEnum eq_mode); static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2); static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2); static JSValue JS_ToObject(JSContext *ctx, JSValueConst val); static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); static JSValue js_cell_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSProperty *add_property(JSContext *ctx, JSObject *p, JSAtom prop, int prop_flags); JSValue JS_ThrowOutOfMemory(JSContext *ctx); static int JS_CreateProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags); static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, int pos2, int len); static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, BOOL is_arg); static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); static int js_string_compare(JSContext *ctx, const JSString *p1, const JSString *p2); static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val); static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, JSValue prop, JSValue val, int flags); static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, JSObject *p, JSAtom prop); static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); static void JS_AddIntrinsicBasicObjects(JSContext *ctx); static void js_free_shape(JSRuntime *rt, JSShape *sh); static void js_free_shape_null(JSRuntime *rt, JSShape *sh); static int js_shape_prepare_update(JSContext *ctx, JSObject *p, JSShapeProperty **pprs); static int init_shape_hash(JSRuntime *rt); static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, JSValueConst obj); static __exception int js_get_length64(JSContext *ctx, int64_t *pres, JSValueConst obj); static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, JSValueConst array_arg); static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, JSValue **arrpp, uint32_t *countp); static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val); static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst *argv, int flags); static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val); static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, JSGCObjectTypeEnum type); static void remove_gc_object(JSGCObjectHeader *h); static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); static void JS_RunGCInternal(JSRuntime *rt); static int js_string_find_invalid_codepoint(JSString *p); static JSValue js_regexp_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static const JSClassExoticMethods js_string_exotic_methods; JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; static void js_trigger_gc(JSRuntime *rt, size_t size) { BOOL force_gc; #ifdef FORCE_GC_AT_MALLOC force_gc = TRUE; #else force_gc = ((rt->malloc_state.malloc_size + size) > rt->malloc_gc_threshold); #endif if (force_gc) { #ifdef DUMP_GC printf("GC: size=%" PRIu64 "\n", (uint64_t)rt->malloc_state.malloc_size); #endif JS_RunGC(rt); rt->malloc_gc_threshold = rt->malloc_state.malloc_size + (rt->malloc_state.malloc_size >> 1); } } static size_t js_malloc_usable_size_unknown(const void *ptr) { return 0; } void *js_malloc_rt(JSRuntime *rt, size_t size) { return rt->mf.js_malloc(&rt->malloc_state, size); } void js_free_rt(JSRuntime *rt, void *ptr) { rt->mf.js_free(&rt->malloc_state, ptr); } void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) { return rt->mf.js_realloc(&rt->malloc_state, ptr, size); } size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) { return rt->mf.js_malloc_usable_size(ptr); } void *js_mallocz_rt(JSRuntime *rt, size_t size) { void *ptr; ptr = js_malloc_rt(rt, size); if (!ptr) return NULL; return memset(ptr, 0, size); } /* Throw out of memory in case of error */ void *js_malloc(JSContext *ctx, size_t size) { void *ptr; ptr = js_malloc_rt(ctx->rt, size); if (unlikely(!ptr)) { JS_ThrowOutOfMemory(ctx); return NULL; } return ptr; } /* Throw out of memory in case of error */ void *js_mallocz(JSContext *ctx, size_t size) { void *ptr; ptr = js_mallocz_rt(ctx->rt, size); if (unlikely(!ptr)) { JS_ThrowOutOfMemory(ctx); return NULL; } return ptr; } void js_free(JSContext *ctx, void *ptr) { js_free_rt(ctx->rt, ptr); } /* Throw out of memory in case of error */ void *js_realloc(JSContext *ctx, void *ptr, size_t size) { void *ret; ret = js_realloc_rt(ctx->rt, ptr, size); if (unlikely(!ret && size != 0)) { JS_ThrowOutOfMemory(ctx); return NULL; } return ret; } /* store extra allocated size in *pslack if successful */ void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack) { void *ret; ret = js_realloc_rt(ctx->rt, ptr, size); if (unlikely(!ret && size != 0)) { JS_ThrowOutOfMemory(ctx); return NULL; } if (pslack) { size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret); *pslack = (new_size > size) ? new_size - size : 0; } return ret; } size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) { return js_malloc_usable_size_rt(ctx->rt, ptr); } /* Throw out of memory exception in case of error */ char *js_strndup(JSContext *ctx, const char *s, size_t n) { char *ptr; ptr = js_malloc(ctx, n + 1); if (ptr) { memcpy(ptr, s, n); ptr[n] = '\0'; } return ptr; } char *js_strdup(JSContext *ctx, const char *str) { return js_strndup(ctx, str, strlen(str)); } static no_inline int js_realloc_array(JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { int new_size; size_t slack; void *new_array; /* XXX: potential arithmetic overflow */ new_size = max_int(req_size, *psize * 3 / 2); new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); if (!new_array) return -1; new_size += slack / elem_size; *psize = new_size; *parray = new_array; return 0; } /* resize the array and update its size if req_size > *psize */ static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { if (unlikely(req_size > *psize)) return js_realloc_array(ctx, parray, elem_size, psize, req_size); else return 0; } static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) { dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt); } static inline int is_digit(int c) { return c >= '0' && c <= '9'; } static inline int string_get(const JSString *p, int idx) { return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx]; } typedef struct JSClassShortDef { JSAtom class_name; JSClassFinalizer *finalizer; JSClassGCMark *gc_mark; } JSClassShortDef; static JSClassShortDef const js_std_class_def[] = { { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ }; static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, int start, int count) { JSClassDef cm_s, *cm = &cm_s; int i, class_id; for(i = 0; i < count; i++) { class_id = i + start; memset(cm, 0, sizeof(*cm)); cm->finalizer = tab[i].finalizer; cm->gc_mark = tab[i].gc_mark; if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) return -1; } return 0; } #if !defined(CONFIG_STACK_CHECK) /* no stack limitation */ static inline uintptr_t js_get_stack_pointer(void) { return 0; } static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) { return FALSE; } #else /* Note: OS and CPU dependent */ static inline uintptr_t js_get_stack_pointer(void) { return (uintptr_t)__builtin_frame_address(0); } static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) { uintptr_t sp; sp = js_get_stack_pointer() - alloca_size; return unlikely(sp < rt->stack_limit); } #endif JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) { JSRuntime *rt; JSMallocState ms; memset(&ms, 0, sizeof(ms)); ms.opaque = opaque; ms.malloc_limit = -1; rt = mf->js_malloc(&ms, sizeof(JSRuntime)); if (!rt) return NULL; memset(rt, 0, sizeof(*rt)); rt->mf = *mf; if (!rt->mf.js_malloc_usable_size) { /* use dummy function if none provided */ rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; } rt->malloc_state = ms; rt->malloc_gc_threshold = 256 * 1024; init_list_head(&rt->context_list); init_list_head(&rt->gc_obj_list); init_list_head(&rt->gc_zero_ref_count_list); rt->gc_phase = JS_GC_PHASE_NONE; #ifdef DUMP_LEAKS init_list_head(&rt->string_list); #endif if (JS_InitAtoms(rt)) goto fail; /* create the object, array and function classes */ if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, countof(js_std_class_def)) < 0) goto fail; rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call; rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; if (init_shape_hash(rt)) goto fail; rt->stack_size = JS_DEFAULT_STACK_SIZE; JS_UpdateStackTop(rt); rt->current_exception = JS_UNINITIALIZED; #ifdef DUMP_PROFILE /* Initialize profiling state */ memset(&rt->profile, 0, sizeof(rt->profile)); rt->profile.sample_capacity = MAX_PROFILE_SAMPLES; rt->profile.samples = mf->js_malloc(&rt->malloc_state, sizeof(JSProfileSample) * rt->profile.sample_capacity); if (!rt->profile.samples) { rt->profile.sample_capacity = 0; } rt->profile.sample_interval_us = 2000; /* 2ms default */ gettimeofday(&rt->profile.last_sample_time, NULL); #endif return rt; fail: JS_FreeRuntime(rt); return NULL; } void *JS_GetRuntimeOpaque(JSRuntime *rt) { return rt->user_opaque; } void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) { rt->user_opaque = opaque; } /* default memory allocation functions with memory limitation */ static size_t js_def_malloc_usable_size(const void *ptr) { #if defined(__APPLE__) return malloc_size(ptr); #elif defined(_WIN32) return _msize((void *)ptr); #elif defined(EMSCRIPTEN) return 0; #elif defined(__linux__) || defined(__GLIBC__) return malloc_usable_size((void *)ptr); #else /* change this to `return 0;` if compilation fails */ return malloc_usable_size((void *)ptr); #endif } static void *js_def_malloc(JSMallocState *s, size_t size) { void *ptr; /* Do not allocate zero bytes: behavior is platform dependent */ assert(size != 0); if (unlikely(s->malloc_size + size > s->malloc_limit)) return NULL; ptr = malloc(size); if (!ptr) return NULL; s->malloc_count++; s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; return ptr; } static void js_def_free(JSMallocState *s, void *ptr) { if (!ptr) return; s->malloc_count--; s->malloc_size -= js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; free(ptr); } static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size) { size_t old_size; if (!ptr) { if (size == 0) return NULL; return js_def_malloc(s, size); } old_size = js_def_malloc_usable_size(ptr); if (size == 0) { s->malloc_count--; s->malloc_size -= old_size + MALLOC_OVERHEAD; free(ptr); return NULL; } if (s->malloc_size + size - old_size > s->malloc_limit) return NULL; ptr = realloc(ptr, size); if (!ptr) return NULL; s->malloc_size += js_def_malloc_usable_size(ptr) - old_size; return ptr; } static const JSMallocFunctions def_malloc_funcs = { js_def_malloc, js_def_free, js_def_realloc, js_def_malloc_usable_size, }; JSRuntime *JS_NewRuntime(void) { return JS_NewRuntime2(&def_malloc_funcs, NULL); } void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) { rt->malloc_state.malloc_limit = limit; } /* use -1 to disable automatic GC */ void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) { rt->malloc_gc_threshold = gc_threshold; } /* Helper to call system free (for memory allocated by external libs like blob.h) */ static void sys_free(void *ptr) { free(ptr); } #define malloc(s) malloc_is_forbidden(s) #define free(p) free_is_forbidden(p) #define realloc(p,s) realloc_is_forbidden(p,s) void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) { rt->interrupt_handler = cb; rt->interrupt_opaque = opaque; } void JS_SetStripInfo(JSRuntime *rt, int flags) { rt->strip_flags = flags; } int JS_GetStripInfo(JSRuntime *rt) { return rt->strip_flags; } static inline uint32_t atom_get_free(const JSAtomStruct *p) { return (uintptr_t)p >> 1; } static inline BOOL atom_is_free(const JSAtomStruct *p) { return (uintptr_t)p & 1; } static inline JSAtomStruct *atom_set_free(uint32_t v) { return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); } /* Note: the string contents are uninitialized */ static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) { JSString *str; str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); if (unlikely(!str)) return NULL; str->header.ref_count = 1; str->is_wide_char = is_wide_char; str->len = max_len; str->atom_type = 0; str->hash = 0; /* optional but costless */ str->hash_next = 0; /* optional */ #ifdef DUMP_LEAKS list_add_tail(&str->link, &rt->string_list); #endif return str; } static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) { JSString *p; p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); if (unlikely(!p)) { JS_ThrowOutOfMemory(ctx); return NULL; } return p; } /* same as JS_FreeValueRT() but faster */ static inline void js_free_string(JSRuntime *rt, JSString *str) { if (--str->header.ref_count <= 0) { if (str->atom_type) { JS_FreeAtomStruct(rt, str); } else { #ifdef DUMP_LEAKS list_del(&str->link); #endif js_free_rt(rt, str); } } } void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) { if (rt) rt->rt_info = s; } void JS_FreeRuntime(JSRuntime *rt) { struct list_head *el, *el1; int i; JS_FreeValueRT(rt, rt->current_exception); JS_RunGCInternal(rt); #ifdef DUMP_LEAKS /* leaking objects */ { BOOL header_done; JSGCObjectHeader *p; int count; /* remove the internal refcounts to display only the object referenced externally */ list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); p->mark = 0; } gc_decref(rt); header_done = FALSE; list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); if (p->ref_count != 0) { if (!header_done) { printf("Object leaks:\n"); JS_DumpObjectHeader(rt); header_done = TRUE; } JS_DumpGCObject(rt, p); } } count = 0; list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); if (p->ref_count == 0) { count++; } } if (count != 0) printf("Secondary object leaks: %d\n", count); } #endif // if (!list_empty(&rt->gc_obj_list)) // printf("Object leaks!\n"); // assert(list_empty(&rt->gc_obj_list)); /* free the classes */ for(i = 0; i < rt->class_count; i++) { JSClass *cl = &rt->class_array[i]; if (cl->class_id != 0) { JS_FreeAtomRT(rt, cl->class_name); } } js_free_rt(rt, rt->class_array); #ifdef DUMP_LEAKS /* only the atoms defined in JS_InitAtoms() should be left */ { BOOL header_done = FALSE; for(i = 0; i < rt->atom_size; i++) { JSAtomStruct *p = rt->atom_array[i]; if (!atom_is_free(p) /* && p->str*/) { if (i >= JS_ATOM_END || p->header.ref_count != 1) { if (!header_done) { header_done = TRUE; if (rt->rt_info) { printf("%s:1: atom leakage:", rt->rt_info); } else { printf("Atom leaks:\n" " %6s %6s %s\n", "ID", "REFCNT", "NAME"); } } if (rt->rt_info) { printf(" "); } else { printf(" %6u %6u ", i, p->header.ref_count); } switch (p->atom_type) { case JS_ATOM_TYPE_STRING: JS_DumpString(rt, p); break; case JS_ATOM_TYPE_SYMBOL: printf("Symbol("); JS_DumpString(rt, p); printf(")"); break; } if (rt->rt_info) { printf(":%u", p->header.ref_count); } else { printf("\n"); } } } } if (rt->rt_info && header_done) printf("\n"); } #endif /* free the atoms */ for(i = 0; i < rt->atom_size; i++) { JSAtomStruct *p = rt->atom_array[i]; if (!atom_is_free(p)) { #ifdef DUMP_LEAKS list_del(&p->link); #endif js_free_rt(rt, p); } } js_free_rt(rt, rt->atom_array); js_free_rt(rt, rt->atom_hash); js_free_rt(rt, rt->shape_hash); #ifdef DUMP_LEAKS if (!list_empty(&rt->string_list)) { if (rt->rt_info) { printf("%s:1: string leakage:", rt->rt_info); } else { printf("String leaks:\n" " %6s %s\n", "REFCNT", "VALUE"); } list_for_each_safe(el, el1, &rt->string_list) { JSString *str = list_entry(el, JSString, link); if (rt->rt_info) { printf(" "); } else { printf(" %6u ", str->header.ref_count); } JS_DumpString(rt, str); if (rt->rt_info) { printf(":%u", str->header.ref_count); } else { printf("\n"); } list_del(&str->link); js_free_rt(rt, str); } if (rt->rt_info) printf("\n"); } { JSMallocState *s = &rt->malloc_state; if (s->malloc_count > 1) { if (rt->rt_info) printf("%s:1: ", rt->rt_info); printf("Memory leak: %"PRIu64" bytes lost in %"PRIu64" block%s\n", (uint64_t)(s->malloc_size - sizeof(JSRuntime)), (uint64_t)(s->malloc_count - 1), &"s"[s->malloc_count == 2]); } } #endif #ifdef DUMP_PROFILE /* Dump profiling statistics */ { struct list_head *el; JSGCObjectHeader *p; uint32_t func_count = 0; uint32_t total_funcs = 0; printf("\n=== PROFILING STATISTICS ===\n\n"); /* Dump sampling profiler results */ if (rt->profile.sample_count > 0) { printf("Sampling Profile (%u samples, interval=%uus):\n", rt->profile.sample_count, rt->profile.sample_interval_us); printf(" %-50s %10s %10s\n", "Function", "Samples", "Percent"); printf(" %s\n", "------------------------------------------------------------------------"); /* Count samples per function */ list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); if (p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { JSFunctionBytecode *b = (JSFunctionBytecode *)p; uint32_t sample_hits = 0; uint32_t i; for (i = 0; i < rt->profile.sample_count; i++) { if (rt->profile.samples[i].func == b) { sample_hits++; } } if (sample_hits > 0) { JSContext *ctx = b->realm ? b->realm : rt->js; const char *fname = ctx ? JS_AtomToCString(ctx, b->func_name) : NULL; double percent = (100.0 * sample_hits) / rt->profile.sample_count; printf(" %-50s %10u %9.2f%%\n", fname ? fname : "", sample_hits, percent); if (fname && ctx) JS_FreeCString(ctx, fname); } } } printf("\n"); } /* Dump function counters */ printf("Function Statistics:\n"); printf(" %-50s %12s %15s\n", "Function", "Calls", "Instructions"); printf(" %s\n", "------------------------------------------------------------------------"); list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); if (p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { JSFunctionBytecode *b = (JSFunctionBytecode *)p; total_funcs++; if (b->profile.entry_count > 0 || b->profile.self_ticks > 0) { JSContext *ctx = b->realm ? b->realm : rt->js; const char *fname = ctx ? JS_AtomToCString(ctx, b->func_name) : NULL; printf(" %-50s %12llu %15llu\n", fname ? fname : "", (unsigned long long)b->profile.entry_count, (unsigned long long)b->profile.self_ticks); if (fname && ctx) JS_FreeCString(ctx, fname); func_count++; /* Dump hot callsites */ if (b->call_site_count > 0) { uint32_t i; printf(" Call sites:\n"); for (i = 0; i < b->call_site_count && i < 5; i++) { printf(" PC %6u: %10llu calls\n", b->call_sites[i].pc_offset, (unsigned long long)b->call_sites[i].hit_count); } } /* Dump hot property sites */ if (b->prop_site_count > 0) { uint32_t i; printf(" Property sites:\n"); for (i = 0; i < b->prop_site_count && i < 5; i++) { JSContext *pctx = b->realm ? b->realm : rt->js; const char *pname = pctx ? JS_AtomToCString(pctx, b->prop_sites[i].atom) : NULL; printf(" PC %6u %-20s: %10llu accesses\n", b->prop_sites[i].pc_offset, pname ? pname : "", (unsigned long long)b->prop_sites[i].hit_count); if (pname && pctx) JS_FreeCString(pctx, pname); } } } } } if (func_count == 0) { printf(" (no functions executed) [%u total functions found]\n", total_funcs); } printf("\n"); /* Free profiling data */ js_free_rt(rt, rt->profile.samples); } #endif { JSMallocState ms = rt->malloc_state; rt->mf.js_free(&ms, rt); } } JSContext *JS_NewContextRaw(JSRuntime *rt) { assert(!rt->js); JSContext *ctx; int i; ctx = js_mallocz_rt(rt, sizeof(JSContext)); if (!ctx) return NULL; ctx->header.ref_count = 1; ctx->trace_hook = NULL; add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * rt->class_count); if (!ctx->class_proto) { js_free_rt(rt, ctx); return NULL; } ctx->rt = rt; list_add_tail(&ctx->link, &rt->context_list); for(i = 0; i < rt->class_count; i++) ctx->class_proto[i] = JS_NULL; ctx->array_ctor = JS_NULL; ctx->regexp_ctor = JS_NULL; /* Initialize VM stacks for trampoline */ ctx->frame_stack_capacity = 512; ctx->frame_stack = js_malloc_rt(rt, sizeof(struct VMFrame) * ctx->frame_stack_capacity); if (!ctx->frame_stack) { js_free_rt(rt, ctx->class_proto); js_free_rt(rt, ctx); return NULL; } ctx->frame_stack_top = -1; ctx->value_stack_capacity = 65536; /* 64K JSValue slots */ ctx->value_stack = js_malloc_rt(rt, sizeof(JSValue) * ctx->value_stack_capacity); if (!ctx->value_stack) { js_free_rt(rt, ctx->frame_stack); js_free_rt(rt, ctx->class_proto); js_free_rt(rt, ctx); return NULL; } ctx->value_stack_top = 0; JS_AddIntrinsicBasicObjects(ctx); rt->js = ctx; return ctx; } JSContext *JS_NewContext(JSRuntime *rt) { JSContext *ctx; ctx = JS_NewContextRaw(rt); if (!ctx) return NULL; JS_AddIntrinsicBaseObjects(ctx); JS_AddIntrinsicEval(ctx); JS_AddIntrinsicRegExp(ctx); return ctx; } void *JS_GetContextOpaque(JSContext *ctx) { return ctx->user_opaque; } void JS_SetContextOpaque(JSContext *ctx, void *opaque) { ctx->user_opaque = opaque; } /* set the new value and free the old value after (freeing the value can reallocate the object data) */ static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) { JSValue old_val; old_val = *pval; *pval = new_val; JS_FreeValue(ctx, old_val); } void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) { JSRuntime *rt = ctx->rt; assert(class_id < rt->class_count); set_value(ctx, &ctx->class_proto[class_id], obj); } JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) { JSRuntime *rt = ctx->rt; assert(class_id < rt->class_count); return JS_DupValue(ctx, ctx->class_proto[class_id]); } JSContext *JS_DupContext(JSContext *ctx) { ctx->header.ref_count++; return ctx; } /* used by the GC */ static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, JS_MarkFunc *mark_func) { int i; JS_MarkValue(rt, ctx->global_obj, mark_func); JS_MarkValue(rt, ctx->global_var_obj, mark_func); JS_MarkValue(rt, ctx->throw_type_error, mark_func); JS_MarkValue(rt, ctx->eval_obj, mark_func); JS_MarkValue(rt, ctx->array_proto_values, mark_func); for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); } for(i = 0; i < rt->class_count; i++) { JS_MarkValue(rt, ctx->class_proto[i], mark_func); } JS_MarkValue(rt, ctx->array_ctor, mark_func); JS_MarkValue(rt, ctx->regexp_ctor, mark_func); JS_MarkValue(rt, ctx->function_ctor, mark_func); JS_MarkValue(rt, ctx->function_proto, mark_func); if (ctx->array_shape) mark_func(rt, &ctx->array_shape->header); } void JS_FreeContext(JSContext *ctx) { JSRuntime *rt = ctx->rt; int i; if (--ctx->header.ref_count > 0) return; assert(ctx->header.ref_count == 0); #ifdef DUMP_ATOMS JS_DumpAtoms(ctx->rt); #endif #ifdef DUMP_SHAPES JS_DumpShapes(ctx->rt); #endif #ifdef DUMP_OBJECTS { struct list_head *el; JSGCObjectHeader *p; printf("JSObjects: {\n"); JS_DumpObjectHeader(ctx->rt); list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); JS_DumpGCObject(rt, p); } printf("}\n"); } #endif #ifdef DUMP_MEM { JSMemoryUsage stats; JS_ComputeMemoryUsage(rt, &stats); JS_DumpMemoryUsage(stdout, &stats, rt); } #endif JS_FreeValue(ctx, ctx->global_obj); JS_FreeValue(ctx, ctx->global_var_obj); JS_FreeValue(ctx, ctx->throw_type_error); JS_FreeValue(ctx, ctx->eval_obj); JS_FreeValue(ctx, ctx->array_proto_values); for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { JS_FreeValue(ctx, ctx->native_error_proto[i]); } for(i = 0; i < rt->class_count; i++) { JS_FreeValue(ctx, ctx->class_proto[i]); } js_free_rt(rt, ctx->class_proto); JS_FreeValue(ctx, ctx->array_ctor); JS_FreeValue(ctx, ctx->regexp_ctor); JS_FreeValue(ctx, ctx->function_ctor); JS_FreeValue(ctx, ctx->function_proto); js_free_shape_null(ctx->rt, ctx->array_shape); /* Free VM stacks */ if (ctx->frame_stack) js_free_rt(rt, ctx->frame_stack); if (ctx->value_stack) js_free_rt(rt, ctx->value_stack); list_del(&ctx->link); remove_gc_object(&ctx->header); js_free_rt(ctx->rt, ctx); } JSRuntime *JS_GetRuntime(JSContext *ctx) { return ctx->rt; } static void update_stack_limit(JSRuntime *rt) { if (rt->stack_size == 0) { rt->stack_limit = 0; /* no limit */ } else { rt->stack_limit = rt->stack_top - rt->stack_size; } } void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) { rt->stack_size = stack_size; update_stack_limit(rt); } void JS_UpdateStackTop(JSRuntime *rt) { rt->stack_top = js_get_stack_pointer(); update_stack_limit(rt); } /* JSAtom support */ #define JS_ATOM_TAG_INT (1U << 31) #define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) #define JS_ATOM_MAX ((1U << 30) - 1) /* return the max count from the hash size */ #define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) static inline BOOL __JS_AtomIsConst(JSAtom v) { #if defined(DUMP_LEAKS) && DUMP_LEAKS > 1 return (int32_t)v <= 0; #else return (int32_t)v < JS_ATOM_END; #endif } static inline BOOL __JS_AtomIsTaggedInt(JSAtom v) { return (v & JS_ATOM_TAG_INT) != 0; } static inline JSAtom __JS_AtomFromUInt32(uint32_t v) { return v | JS_ATOM_TAG_INT; } static inline uint32_t __JS_AtomToUInt32(JSAtom atom) { return atom & ~JS_ATOM_TAG_INT; } static inline int is_num(int c) { return c >= '0' && c <= '9'; } /* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */ static inline BOOL is_num_string(uint32_t *pval, const JSString *p) { uint32_t n; uint64_t n64; int c, i, len; len = p->len; if (len == 0 || len > 10) return FALSE; c = string_get(p, 0); if (is_num(c)) { if (c == '0') { if (len != 1) return FALSE; n = 0; } else { n = c - '0'; for(i = 1; i < len; i++) { c = string_get(p, i); if (!is_num(c)) return FALSE; n64 = (uint64_t)n * 10 + (c - '0'); if ((n64 >> 32) != 0) return FALSE; n = n64; } } *pval = n; return TRUE; } else { return FALSE; } } /* XXX: could use faster version ? */ static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) { size_t i; for(i = 0; i < len; i++) h = h * 263 + str[i]; return h; } static inline uint32_t hash_string16(const uint16_t *str, size_t len, uint32_t h) { size_t i; for(i = 0; i < len; i++) h = h * 263 + str[i]; return h; } static uint32_t hash_string(const JSString *str, uint32_t h) { if (str->is_wide_char) h = hash_string16(str->u.str16, str->len, h); else h = hash_string8(str->u.str8, str->len, h); return h; } static uint32_t hash_string_rope(JSValueConst val, uint32_t h) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { return hash_string(JS_VALUE_GET_STRING(val), h); } else { JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); h = hash_string_rope(r->left, h); return hash_string_rope(r->right, h); } } static __maybe_unused void JS_DumpChar(FILE *fo, int c, int sep) { if (c == sep || c == '\\') { fputc('\\', fo); fputc(c, fo); } else if (c >= ' ' && c <= 126) { fputc(c, fo); } else if (c == '\n') { fputc('\\', fo); fputc('n', fo); } else { fprintf(fo, "\\u%04x", c); } } static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) { int i, sep; if (p == NULL) { printf(""); return; } printf("%d", p->header.ref_count); sep = (p->header.ref_count == 1) ? '\"' : '\''; putchar(sep); for(i = 0; i < p->len; i++) { JS_DumpChar(stdout, string_get(p, i), sep); } putchar(sep); } static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) { JSAtomStruct *p; int h, i; /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ printf("JSAtom count=%d size=%d hash_size=%d:\n", rt->atom_count, rt->atom_size, rt->atom_hash_size); printf("JSAtom hash table: {\n"); for(i = 0; i < rt->atom_hash_size; i++) { h = rt->atom_hash[i]; if (h) { printf(" %d:", i); while (h) { p = rt->atom_array[h]; printf(" "); JS_DumpString(rt, p); h = p->hash_next; } printf("\n"); } } printf("}\n"); printf("JSAtom table: {\n"); for(i = 0; i < rt->atom_size; i++) { p = rt->atom_array[i]; if (!atom_is_free(p)) { printf(" %d: { %d %08x ", i, p->atom_type, p->hash); if (!(p->len == 0 && p->is_wide_char != 0)) JS_DumpString(rt, p); printf(" %d }\n", p->hash_next); } } printf("}\n"); } static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) { JSAtomStruct *p; uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ new_hash_mask = new_hash_size - 1; new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); if (!new_hash) return -1; for(i = 0; i < rt->atom_hash_size; i++) { h = rt->atom_hash[i]; while (h != 0) { p = rt->atom_array[h]; hash_next1 = p->hash_next; /* add in new hash table */ j = p->hash & new_hash_mask; p->hash_next = new_hash[j]; new_hash[j] = h; h = hash_next1; } } js_free_rt(rt, rt->atom_hash); rt->atom_hash = new_hash; rt->atom_hash_size = new_hash_size; rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); // JS_DumpAtoms(rt); return 0; } static int JS_InitAtoms(JSRuntime *rt) { int i, len, atom_type; const char *p; rt->atom_hash_size = 0; rt->atom_hash = NULL; rt->atom_count = 0; rt->atom_size = 0; rt->atom_free_index = 0; if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ return -1; p = js_atom_init; for(i = 1; i < JS_ATOM_END; i++) { if (i >= JS_ATOM_Symbol_toPrimitive) atom_type = JS_ATOM_TYPE_SYMBOL; else atom_type = JS_ATOM_TYPE_STRING; len = strlen(p); if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) return -1; p = p + len + 1; } return 0; } static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) { JSAtomStruct *p; if (!__JS_AtomIsConst(v)) { p = rt->atom_array[v]; p->header.ref_count++; } return v; } JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) { JSRuntime *rt; JSAtomStruct *p; if (!__JS_AtomIsConst(v)) { rt = ctx->rt; p = rt->atom_array[v]; p->header.ref_count++; } return v; } static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) { JSRuntime *rt; JSAtomStruct *p; rt = ctx->rt; if (__JS_AtomIsTaggedInt(v)) return JS_ATOM_KIND_STRING; p = rt->atom_array[v]; switch(p->atom_type) { case JS_ATOM_TYPE_STRING: return JS_ATOM_KIND_STRING; case JS_ATOM_TYPE_SYMBOL: return JS_ATOM_KIND_SYMBOL; default: abort(); } } static BOOL JS_AtomIsString(JSContext *ctx, JSAtom v) { return JS_AtomGetKind(ctx, v) == JS_ATOM_KIND_STRING; } static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) { uint32_t i = p->hash_next; /* atom_index */ if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { JSAtomStruct *p1; i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; p1 = rt->atom_array[i]; while (p1 != p) { assert(i != 0); i = p1->hash_next; p1 = rt->atom_array[i]; } } return i; } /* string case (internal). Return JS_ATOM_NULL if error. 'str' is freed. */ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) { uint32_t h, h1, i; JSAtomStruct *p; int len; if (atom_type < JS_ATOM_TYPE_SYMBOL) { /* str is not NULL */ if (str->atom_type == atom_type) { /* str is the atom, return its index */ i = js_get_atom_index(rt, str); /* reduce string refcount and increase atom's unless constant */ if (__JS_AtomIsConst(i)) str->header.ref_count--; return i; } /* try and locate an already registered atom */ len = str->len; h = hash_string(str, atom_type); h &= JS_ATOM_HASH_MASK; h1 = h & (rt->atom_hash_size - 1); i = rt->atom_hash[h1]; while (i != 0) { p = rt->atom_array[i]; if (p->hash == h && p->atom_type == atom_type && p->len == len && js_string_memcmp(p, 0, str, 0, len) == 0) { if (!__JS_AtomIsConst(i)) p->header.ref_count++; goto done; } i = p->hash_next; } } else { h = 0; h1 = 0; } if (rt->atom_free_index == 0) { /* allow new atom entries */ uint32_t new_size, start; JSAtomStruct **new_array; /* alloc new with size progression 3/2: 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 preallocating space for predefined atoms (at least 195). */ new_size = max_int(211, rt->atom_size * 3 / 2); if (new_size > JS_ATOM_MAX) goto fail; /* XXX: should use realloc2 to use slack space */ new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); if (!new_array) goto fail; /* Note: the atom 0 is not used */ start = rt->atom_size; if (start == 0) { /* JS_ATOM_NULL entry */ p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); if (!p) { js_free_rt(rt, new_array); goto fail; } p->header.ref_count = 1; /* not refcounted */ p->atom_type = JS_ATOM_TYPE_SYMBOL; #ifdef DUMP_LEAKS list_add_tail(&p->link, &rt->string_list); #endif new_array[0] = p; rt->atom_count++; start = 1; } rt->atom_size = new_size; rt->atom_array = new_array; rt->atom_free_index = start; for(i = start; i < new_size; i++) { uint32_t next; if (i == (new_size - 1)) next = 0; else next = i + 1; rt->atom_array[i] = atom_set_free(next); } } if (str) { if (str->atom_type == 0) { p = str; p->atom_type = atom_type; } else { p = js_malloc_rt(rt, sizeof(JSString) + (str->len << str->is_wide_char) + 1 - str->is_wide_char); if (unlikely(!p)) goto fail; p->header.ref_count = 1; p->is_wide_char = str->is_wide_char; p->len = str->len; #ifdef DUMP_LEAKS list_add_tail(&p->link, &rt->string_list); #endif memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) + 1 - str->is_wide_char); js_free_string(rt, str); } } else { /* Allocate extended JSAtomSymbol for symbol atoms */ JSAtomSymbol *sp; sp = js_malloc_rt(rt, sizeof(JSAtomSymbol)); if (!sp) return JS_ATOM_NULL; p = &sp->s; p->header.ref_count = 1; p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ p->len = 0; sp->obj_key = JS_NULL; #ifdef DUMP_LEAKS list_add_tail(&p->link, &rt->string_list); #endif } /* use an already free entry */ i = rt->atom_free_index; rt->atom_free_index = atom_get_free(rt->atom_array[i]); rt->atom_array[i] = p; p->hash = h; p->hash_next = i; /* atom_index */ p->atom_type = atom_type; rt->atom_count++; if (atom_type != JS_ATOM_TYPE_SYMBOL) { p->hash_next = rt->atom_hash[h1]; rt->atom_hash[h1] = i; if (unlikely(rt->atom_count >= rt->atom_count_resize)) JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); } // JS_DumpAtoms(rt); return i; fail: i = JS_ATOM_NULL; done: if (str) js_free_string(rt, str); return i; } /* only works with zero terminated 8 bit strings */ static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, int atom_type) { JSString *p; p = js_alloc_string_rt(rt, len, 0); if (!p) return JS_ATOM_NULL; memcpy(p->u.str8, str, len); p->u.str8[len] = '\0'; return __JS_NewAtom(rt, p, atom_type); } /* Warning: str must be ASCII only */ static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, int atom_type) { uint32_t h, h1, i; JSAtomStruct *p; h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); h &= JS_ATOM_HASH_MASK; h1 = h & (rt->atom_hash_size - 1); i = rt->atom_hash[h1]; while (i != 0) { p = rt->atom_array[i]; if (p->hash == h && p->atom_type == JS_ATOM_TYPE_STRING && p->len == len && p->is_wide_char == 0 && memcmp(p->u.str8, str, len) == 0) { if (!__JS_AtomIsConst(i)) p->header.ref_count++; return i; } i = p->hash_next; } return JS_ATOM_NULL; } static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) { uint32_t i = p->hash_next; /* atom_index */ if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { JSAtomStruct *p0, *p1; uint32_t h0; h0 = p->hash & (rt->atom_hash_size - 1); i = rt->atom_hash[h0]; p1 = rt->atom_array[i]; if (p1 == p) { rt->atom_hash[h0] = p1->hash_next; } else { for(;;) { assert(i != 0); p0 = p1; i = p1->hash_next; p1 = rt->atom_array[i]; if (p1 == p) { p0->hash_next = p1->hash_next; break; } } } } /* insert in free atom list */ rt->atom_array[i] = atom_set_free(rt->atom_free_index); rt->atom_free_index = i; /* free the string structure */ #ifdef DUMP_LEAKS list_del(&p->link); #endif if (p->atom_type == JS_ATOM_TYPE_SYMBOL && p->hash != 0) { /* live weak references are still present on this object: keep it */ } else { /* Free object-key payload for symbol atoms before freeing struct */ if (p->atom_type == JS_ATOM_TYPE_SYMBOL) { JSAtomSymbol *sp = js_atom_as_symbol(p); if (!JS_IsNull(sp->obj_key)) { JS_FreeValueRT(rt, sp->obj_key); sp->obj_key = JS_NULL; } } js_free_rt(rt, p); } rt->atom_count--; assert(rt->atom_count >= 0); } static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) { JSAtomStruct *p; p = rt->atom_array[i]; if (--p->header.ref_count > 0) return; JS_FreeAtomStruct(rt, p); } /* Get or create a unique symbol atom for using an object as a property key. Returns JS_ATOM_NULL on allocation failure. */ static JSAtom js_get_object_key_atom(JSContext *ctx, JSObject *key_obj) { JSRuntime *rt = ctx->rt; JSAtom atom = key_obj->object_key_atom; /* Validate cached atom (non-owning; may be stale) */ if (atom != JS_ATOM_NULL && atom < (JSAtom)rt->atom_size && rt->atom_array[atom] != NULL && !atom_is_free(rt->atom_array[atom])) { JSAtomStruct *ap = rt->atom_array[atom]; if (ap->atom_type == JS_ATOM_TYPE_SYMBOL) { JSAtomSymbol *sp = js_atom_as_symbol(ap); if (JS_VALUE_GET_TAG(sp->obj_key) == JS_TAG_OBJECT && JS_VALUE_GET_OBJ(sp->obj_key) == key_obj) { return atom; } } } /* Create a fresh symbol atom (passing NULL for symbol str) */ atom = __JS_NewAtom(rt, NULL, JS_ATOM_TYPE_SYMBOL); if (atom == JS_ATOM_NULL) return JS_ATOM_NULL; { JSAtomStruct *ap = rt->atom_array[atom]; JSAtomSymbol *sp = js_atom_as_symbol(ap); sp->obj_key = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, key_obj)); } key_obj->object_key_atom = atom; return atom; } /* Get or create a symbol value for using an object as a property key. Returns JS_EXCEPTION on allocation failure. */ static JSValue js_get_object_key_symbol(JSContext *ctx, JSObject *key_obj) { JSAtom atom = js_get_object_key_atom(ctx, key_obj); if (atom == JS_ATOM_NULL) return JS_ThrowOutOfMemory(ctx); return JS_MKPTR(JS_TAG_SYMBOL, ctx->rt->atom_array[atom]); } /* Check if a symbol atom is an object-key symbol (has obj_key payload) */ static BOOL js_atom_is_object_key_symbol(JSRuntime *rt, JSAtom atom) { if (__JS_AtomIsTaggedInt(atom)) return FALSE; if (atom >= (JSAtom)rt->atom_size) return FALSE; JSAtomStruct *ap = rt->atom_array[atom]; if (!ap || ap->atom_type != JS_ATOM_TYPE_SYMBOL) return FALSE; JSAtomSymbol *sp = js_atom_as_symbol(ap); return !JS_IsNull(sp->obj_key); } /* Warning: 'p' is freed */ static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) { JSRuntime *rt = ctx->rt; uint32_t n; if (is_num_string(&n, p)) { if (n <= JS_ATOM_MAX_INT) { js_free_string(rt, p); return __JS_AtomFromUInt32(n); } } /* XXX: should generate an exception */ return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); } /* XXX: optimize */ static size_t count_ascii(const uint8_t *buf, size_t len) { const uint8_t *p, *p_end; p = buf; p_end = buf + len; while (p < p_end && *p < 128) p++; return p - buf; } /* str is UTF-8 encoded */ JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) { JSValue val; if (len == 0 || (!is_digit(*str) && count_ascii((const uint8_t *)str, len) == len)) { JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); if (atom) return atom; } val = JS_NewStringLen(ctx, str, len); if (JS_IsException(val)) return JS_ATOM_NULL; return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); } JSAtom JS_NewAtom(JSContext *ctx, const char *str) { return JS_NewAtomLen(ctx, str, strlen(str)); } JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) { if (n <= JS_ATOM_MAX_INT) { return __JS_AtomFromUInt32(n); } else { char buf[11]; JSValue val; size_t len; len = u32toa(buf, n); val = js_new_string8_len(ctx, buf, len); if (JS_IsException(val)) return JS_ATOM_NULL; return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), JS_ATOM_TYPE_STRING); } } static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) { if ((uint64_t)n <= JS_ATOM_MAX_INT) { return __JS_AtomFromUInt32((uint32_t)n); } else { char buf[24]; JSValue val; size_t len; len = i64toa(buf, n); val = js_new_string8_len(ctx, buf, len); if (JS_IsException(val)) return JS_ATOM_NULL; return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), JS_ATOM_TYPE_STRING); } } /* 'p' is freed */ static JSValue JS_NewSymbol(JSContext *ctx, JSString *p, int atom_type) { JSRuntime *rt = ctx->rt; JSAtom atom; atom = __JS_NewAtom(rt, p, atom_type); if (atom == JS_ATOM_NULL) return JS_ThrowOutOfMemory(ctx); return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); } #define ATOM_GET_STR_BUF_SIZE 64 /* Should only be used for debug. */ static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, JSAtom atom) { if (__JS_AtomIsTaggedInt(atom)) { snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); } else { JSAtomStruct *p; assert(atom < rt->atom_size); if (atom == JS_ATOM_NULL) { snprintf(buf, buf_size, ""); } else { int i, c; char *q; JSString *str; q = buf; p = rt->atom_array[atom]; assert(!atom_is_free(p)); str = p; if (str) { if (!str->is_wide_char) { /* special case ASCII strings */ c = 0; for(i = 0; i < str->len; i++) { c |= str->u.str8[i]; } if (c < 0x80) return (const char *)str->u.str8; } for(i = 0; i < str->len; i++) { c = string_get(str, i); if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX) break; if (c < 128) { *q++ = c; } else { q += unicode_to_utf8((uint8_t *)q, c); } } } *q = '\0'; } } return buf; } static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) { return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); } static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string) { char buf[ATOM_GET_STR_BUF_SIZE]; if (__JS_AtomIsTaggedInt(atom)) { size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); return js_new_string8_len(ctx, buf, len); } else { JSRuntime *rt = ctx->rt; JSAtomStruct *p; assert(atom < rt->atom_size); p = rt->atom_array[atom]; if (p->atom_type == JS_ATOM_TYPE_STRING) { goto ret_string; } else if (force_string) { if (p->len == 0 && p->is_wide_char != 0) { /* no description string */ p = rt->atom_array[JS_ATOM_empty_string]; } ret_string: return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); } else { return JS_DupValue(ctx, JS_MKPTR(JS_TAG_SYMBOL, p)); } } } JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) { return __JS_AtomToValue(ctx, atom, FALSE); } JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) { return __JS_AtomToValue(ctx, atom, TRUE); } /* return TRUE if the atom is an array index (i.e. 0 <= index <= 2^32-2 and return its value */ static BOOL JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) { if (__JS_AtomIsTaggedInt(atom)) { *pval = __JS_AtomToUInt32(atom); return TRUE; } else { JSRuntime *rt = ctx->rt; JSAtomStruct *p; uint32_t val; assert(atom < rt->atom_size); p = rt->atom_array[atom]; if (p->atom_type == JS_ATOM_TYPE_STRING && is_num_string(&val, p) && val != -1) { *pval = val; return TRUE; } else { *pval = 0; return FALSE; } } } /* This test must be fast if atom is not a numeric index (e.g. a method name). Return JS_NULL if not a numeric index. JS_EXCEPTION can also be returned. */ static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) { JSRuntime *rt = ctx->rt; JSAtomStruct *p1; JSString *p; int c, ret; JSValue num, str; if (__JS_AtomIsTaggedInt(atom)) return JS_NewInt32(ctx, __JS_AtomToUInt32(atom)); assert(atom < rt->atom_size); p1 = rt->atom_array[atom]; if (p1->atom_type != JS_ATOM_TYPE_STRING) return JS_NULL; switch(atom) { case JS_ATOM_minus_zero: return __JS_NewFloat64(ctx, -0.0); case JS_ATOM_Infinity: return __JS_NewFloat64(ctx, INFINITY); case JS_ATOM_minus_Infinity: return __JS_NewFloat64(ctx, -INFINITY); case JS_ATOM_NaN: return __JS_NewFloat64(ctx, NAN); default: break; } p = p1; if (p->len == 0) return JS_NULL; c = string_get(p, 0); if (!is_num(c) && c != '-') return JS_NULL; /* this is ECMA CanonicalNumericIndexString primitive */ num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); if (JS_IsException(num)) return num; str = JS_ToString(ctx, num); if (JS_IsException(str)) { JS_FreeValue(ctx, num); return str; } ret = js_string_compare(ctx, p, JS_VALUE_GET_STRING(str)); /* str removed - arg not owned */ if (ret == 0) { return num; } else { JS_FreeValue(ctx, num); return JS_NULL; } } /* return -1 if exception or TRUE/FALSE */ int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) { JSValue num; num = JS_AtomIsNumericIndex1(ctx, atom); if (likely(JS_IsNull(num))) return FALSE; if (JS_IsException(num)) return -1; JS_FreeValue(ctx, num); return TRUE; } void JS_FreeAtom(JSContext *ctx, JSAtom v) { if (!__JS_AtomIsConst(v)) __JS_FreeAtom(ctx->rt, v); } void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) { if (!__JS_AtomIsConst(v)) __JS_FreeAtom(rt, v); } /* return TRUE if 'v' is a symbol with a string description */ static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) { JSRuntime *rt; JSAtomStruct *p; rt = ctx->rt; if (__JS_AtomIsTaggedInt(v)) return FALSE; p = rt->atom_array[v]; return (p->atom_type == JS_ATOM_TYPE_SYMBOL && !(p->len == 0 && p->is_wide_char != 0)); } /* free with JS_FreeCString() */ const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom) { JSValue str; const char *cstr; str = JS_AtomToString(ctx, atom); if (JS_IsException(str)) { if (plen) *plen = 0; return NULL; } cstr = JS_ToCStringLen(ctx, plen, str); /* str removed - arg not owned */ return cstr; } static inline BOOL JS_IsEmptyString(JSValueConst v) { return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; } /* JSClass support */ /* a new class ID is allocated if *pclass_id != 0 */ JSClassID JS_NewClassID(JSClassID *pclass_id) { JSClassID class_id; class_id = *pclass_id; if (class_id == 0) { class_id = js_class_id_alloc++; *pclass_id = class_id; } return class_id; } JSClassID JS_GetClassID(JSValue v) { JSObject *p; if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) return JS_INVALID_CLASS_ID; p = JS_VALUE_GET_OBJ(v); return p->class_id; } BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) { return (class_id < rt->class_count && rt->class_array[class_id].class_id != 0); } /* create a new object internal class. Return -1 if error, 0 if OK. The finalizer can be NULL if none is needed. */ static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def, JSAtom name) { int new_size, i; JSClass *cl, *new_class_array; struct list_head *el; if (class_id >= (1 << 16)) return -1; if (class_id < rt->class_count && rt->class_array[class_id].class_id != 0) return -1; if (class_id >= rt->class_count) { new_size = max_int(JS_CLASS_INIT_COUNT, max_int(class_id + 1, rt->class_count * 3 / 2)); /* reallocate the context class prototype array, if any */ list_for_each(el, &rt->context_list) { JSContext *ctx = list_entry(el, JSContext, link); JSValue *new_tab; new_tab = js_realloc_rt(rt, ctx->class_proto, sizeof(ctx->class_proto[0]) * new_size); if (!new_tab) return -1; for(i = rt->class_count; i < new_size; i++) new_tab[i] = JS_NULL; ctx->class_proto = new_tab; } /* reallocate the class array */ new_class_array = js_realloc_rt(rt, rt->class_array, sizeof(JSClass) * new_size); if (!new_class_array) return -1; memset(new_class_array + rt->class_count, 0, (new_size - rt->class_count) * sizeof(JSClass)); rt->class_array = new_class_array; rt->class_count = new_size; } cl = &rt->class_array[class_id]; cl->class_id = class_id; cl->class_name = JS_DupAtomRT(rt, name); cl->finalizer = class_def->finalizer; cl->gc_mark = class_def->gc_mark; cl->call = class_def->call; cl->exotic = class_def->exotic; return 0; } int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) { int ret, len; JSAtom name; len = strlen(class_def->class_name); name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); if (name == JS_ATOM_NULL) { name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); if (name == JS_ATOM_NULL) return -1; } ret = JS_NewClass1(rt, class_id, class_def, name); JS_FreeAtomRT(rt, name); return ret; } static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) { JSString *str; if (len <= 0) { return JS_AtomToString(ctx, JS_ATOM_empty_string); } str = js_alloc_string(ctx, len, 0); if (!str) return JS_EXCEPTION; memcpy(str->u.str8, buf, len); str->u.str8[len] = '\0'; return JS_MKPTR(JS_TAG_STRING, str); } static JSValue js_new_string8(JSContext *ctx, const char *buf) { return js_new_string8_len(ctx, buf, strlen(buf)); } static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) { JSString *str; str = js_alloc_string(ctx, len, 1); if (!str) return JS_EXCEPTION; memcpy(str->u.str16, buf, len * 2); return JS_MKPTR(JS_TAG_STRING, str); } static JSValue js_new_string_char(JSContext *ctx, uint16_t c) { if (c < 0x100) { uint8_t ch8 = c; return js_new_string8_len(ctx, (const char *)&ch8, 1); } else { uint16_t ch16 = c; return js_new_string16_len(ctx, &ch16, 1); } } static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) { int len = end - start; if (start == 0 && end == p->len) { return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); } if (p->is_wide_char && len > 0) { JSString *str; int i; uint16_t c = 0; for (i = start; i < end; i++) { c |= p->u.str16[i]; } if (c > 0xFF) return js_new_string16_len(ctx, p->u.str16 + start, len); str = js_alloc_string(ctx, len, 0); if (!str) return JS_EXCEPTION; for (i = 0; i < len; i++) { str->u.str8[i] = p->u.str16[start + i]; } str->u.str8[len] = '\0'; return JS_MKPTR(JS_TAG_STRING, str); } else { return js_new_string8_len(ctx, (const char *)(p->u.str8 + start), len); } } typedef struct StringBuffer { JSContext *ctx; JSString *str; int len; int size; int is_wide_char; int error_status; } StringBuffer; /* It is valid to call string_buffer_end() and all string_buffer functions even if string_buffer_init() or another string_buffer function returns an error. If the error_status is set, string_buffer_end() returns JS_EXCEPTION. */ static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, int is_wide) { s->ctx = ctx; s->size = size; s->len = 0; s->is_wide_char = is_wide; s->error_status = 0; s->str = js_alloc_string(ctx, size, is_wide); if (unlikely(!s->str)) { s->size = 0; return s->error_status = -1; } #ifdef DUMP_LEAKS /* the StringBuffer may reallocate the JSString, only link it at the end */ list_del(&s->str->link); #endif return 0; } static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) { return string_buffer_init2(ctx, s, size, 0); } static void string_buffer_free(StringBuffer *s) { js_free(s->ctx, s->str); s->str = NULL; } static int string_buffer_set_error(StringBuffer *s) { js_free(s->ctx, s->str); s->str = NULL; s->size = 0; s->len = 0; return s->error_status = -1; } static no_inline int string_buffer_widen(StringBuffer *s, int size) { JSString *str; size_t slack; int i; if (s->error_status) return -1; str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack); if (!str) return string_buffer_set_error(s); size += slack >> 1; for(i = s->len; i-- > 0;) { str->u.str16[i] = str->u.str8[i]; } s->is_wide_char = 1; s->size = size; s->str = str; return 0; } static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) { JSString *new_str; int new_size; size_t new_size_bytes, slack; if (s->error_status) return -1; if (new_len > JS_STRING_LEN_MAX) { JS_ThrowInternalError(s->ctx, "string too long"); return string_buffer_set_error(s); } new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); if (!s->is_wide_char && c >= 0x100) { return string_buffer_widen(s, new_size); } new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack); if (!new_str) return string_buffer_set_error(s); new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX); s->size = new_size; s->str = new_str; return 0; } static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) { if (unlikely(s->len >= s->size)) { if (string_buffer_realloc(s, s->len + 1, c)) return -1; } if (s->is_wide_char) { s->str->u.str16[s->len++] = c; } else if (c < 0x100) { s->str->u.str8[s->len++] = c; } else { if (string_buffer_widen(s, s->size)) return -1; s->str->u.str16[s->len++] = c; } return 0; } /* 0 <= c <= 0xff */ static int string_buffer_putc8(StringBuffer *s, uint32_t c) { if (unlikely(s->len >= s->size)) { if (string_buffer_realloc(s, s->len + 1, c)) return -1; } if (s->is_wide_char) { s->str->u.str16[s->len++] = c; } else { s->str->u.str8[s->len++] = c; } return 0; } /* 0 <= c <= 0xffff */ static int string_buffer_putc16(StringBuffer *s, uint32_t c) { if (likely(s->len < s->size)) { if (s->is_wide_char) { s->str->u.str16[s->len++] = c; return 0; } else if (c < 0x100) { s->str->u.str8[s->len++] = c; return 0; } } return string_buffer_putc_slow(s, c); } /* 0 <= c <= 0x10ffff */ static int string_buffer_putc(StringBuffer *s, uint32_t c) { if (unlikely(c >= 0x10000)) { /* surrogate pair */ if (string_buffer_putc16(s, get_hi_surrogate(c))) return -1; c = get_lo_surrogate(c); } return string_buffer_putc16(s, c); } static int string_getc(const JSString *p, int *pidx) { int idx, c, c1; idx = *pidx; if (p->is_wide_char) { c = p->u.str16[idx++]; if (is_hi_surrogate(c) && idx < p->len) { c1 = p->u.str16[idx]; if (is_lo_surrogate(c1)) { c = from_surrogate(c, c1); idx++; } } } else { c = p->u.str8[idx++]; } *pidx = idx; return c; } static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) { int i; if (s->len + len > s->size) { if (string_buffer_realloc(s, s->len + len, 0)) return -1; } if (s->is_wide_char) { for (i = 0; i < len; i++) { s->str->u.str16[s->len + i] = p[i]; } s->len += len; } else { memcpy(&s->str->u.str8[s->len], p, len); s->len += len; } return 0; } static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) { int c = 0, i; for (i = 0; i < len; i++) { c |= p[i]; } if (s->len + len > s->size) { if (string_buffer_realloc(s, s->len + len, c)) return -1; } else if (!s->is_wide_char && c >= 0x100) { if (string_buffer_widen(s, s->size)) return -1; } if (s->is_wide_char) { memcpy(&s->str->u.str16[s->len], p, len << 1); s->len += len; } else { for (i = 0; i < len; i++) { s->str->u.str8[s->len + i] = p[i]; } s->len += len; } return 0; } /* appending an ASCII string */ static int string_buffer_puts8(StringBuffer *s, const char *str) { return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); } static int string_buffer_concat(StringBuffer *s, const JSString *p, uint32_t from, uint32_t to) { if (to <= from) return 0; if (p->is_wide_char) return string_buffer_write16(s, p->u.str16 + from, to - from); else return string_buffer_write8(s, p->u.str8 + from, to - from); } static int string_buffer_concat_value(StringBuffer *s, JSValueConst v) { JSString *p; JSValue v1; int res; if (s->error_status) { /* prevent exception overload */ return -1; } if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { if (JS_VALUE_GET_TAG(v) == JS_TAG_STRING_ROPE) { JSStringRope *r = JS_VALUE_GET_STRING_ROPE(v); /* recursion is acceptable because the rope depth is bounded */ if (string_buffer_concat_value(s, r->left)) return -1; return string_buffer_concat_value(s, r->right); } else { v1 = JS_ToString(s->ctx, v); if (JS_IsException(v1)) return string_buffer_set_error(s); p = JS_VALUE_GET_STRING(v1); res = string_buffer_concat(s, p, 0, p->len); JS_FreeValue(s->ctx, v1); return res; } } p = JS_VALUE_GET_STRING(v); return string_buffer_concat(s, p, 0, p->len); } static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) { JSString *p; int res; if (s->error_status) { /* prevent exception overload */ JS_FreeValue(s->ctx, v); return -1; } if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { v = JS_ToStringFree(s->ctx, v); if (JS_IsException(v)) return string_buffer_set_error(s); } p = JS_VALUE_GET_STRING(v); res = string_buffer_concat(s, p, 0, p->len); JS_FreeValue(s->ctx, v); return res; } static int string_buffer_fill(StringBuffer *s, int c, int count) { /* XXX: optimize */ if (s->len + count > s->size) { if (string_buffer_realloc(s, s->len + count, c)) return -1; } while (count-- > 0) { if (string_buffer_putc16(s, c)) return -1; } return 0; } static JSValue string_buffer_end(StringBuffer *s) { JSString *str; str = s->str; if (s->error_status) return JS_EXCEPTION; if (s->len == 0) { js_free(s->ctx, str); s->str = NULL; return JS_AtomToString(s->ctx, JS_ATOM_empty_string); } if (s->len < s->size) { /* smaller size so js_realloc should not fail, but OK if it does */ /* XXX: should add some slack to avoid unnecessary calls */ /* XXX: might need to use malloc+free to ensure smaller size */ str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + (s->len << s->is_wide_char) + 1 - s->is_wide_char); if (str == NULL) str = s->str; s->str = str; } if (!s->is_wide_char) str->u.str8[s->len] = 0; #ifdef DUMP_LEAKS list_add_tail(&str->link, &s->ctx->rt->string_list); #endif str->is_wide_char = s->is_wide_char; str->len = s->len; s->str = NULL; return JS_MKPTR(JS_TAG_STRING, str); } /* create a string from a UTF-8 buffer */ JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) { const uint8_t *p, *p_end, *p_start, *p_next; uint32_t c; StringBuffer b_s, *b = &b_s; size_t len1; p_start = (const uint8_t *)buf; p_end = p_start + buf_len; len1 = count_ascii(p_start, buf_len); p = p_start + len1; if (len1 > JS_STRING_LEN_MAX) return JS_ThrowInternalError(ctx, "string too long"); if (p == p_end) { /* ASCII string */ return js_new_string8_len(ctx, buf, buf_len); } else { if (string_buffer_init(ctx, b, buf_len)) goto fail; string_buffer_write8(b, p_start, len1); while (p < p_end) { if (*p < 128) { string_buffer_putc8(b, *p++); } else { /* parse utf-8 sequence, return 0xFFFFFFFF for error */ c = unicode_from_utf8(p, p_end - p, &p_next); if (c < 0x10000) { p = p_next; } else if (c <= 0x10FFFF) { p = p_next; /* surrogate pair */ string_buffer_putc16(b, get_hi_surrogate(c)); c = get_lo_surrogate(c); } else { /* invalid char */ c = 0xfffd; /* skip the invalid chars */ /* XXX: seems incorrect. Why not just use c = *p++; ? */ while (p < p_end && (*p >= 0x80 && *p < 0xc0)) p++; if (p < p_end) { p++; while (p < p_end && (*p >= 0x80 && *p < 0xc0)) p++; } } string_buffer_putc16(b, c); } } } return string_buffer_end(b); fail: string_buffer_free(b); return JS_EXCEPTION; } static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, JSValue str2, const char *str3) { StringBuffer b_s, *b = &b_s; int len1, len3; JSString *p; if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { str2 = JS_ToStringFree(ctx, str2); if (JS_IsException(str2)) goto fail; } p = JS_VALUE_GET_STRING(str2); len1 = strlen(str1); len3 = strlen(str3); if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) goto fail; string_buffer_write8(b, (const uint8_t *)str1, len1); string_buffer_concat(b, p, 0, p->len); string_buffer_write8(b, (const uint8_t *)str3, len3); JS_FreeValue(ctx, str2); return string_buffer_end(b); fail: JS_FreeValue(ctx, str2); return JS_EXCEPTION; } JSValue JS_NewAtomString(JSContext *ctx, const char *str) { JSAtom atom = JS_NewAtom(ctx, str); if (atom == JS_ATOM_NULL) return JS_EXCEPTION; JSValue val = JS_AtomToString(ctx, atom); JS_FreeAtom(ctx, atom); return val; } /* return (NULL, 0) if exception. */ /* return pointer into a JSString with a live ref_count */ /* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, BOOL cesu8) { JSValue val; JSString *str, *str_new; int pos, len, c, c1; uint8_t *q; if (JS_VALUE_GET_TAG(val1) != JS_TAG_STRING) { val = JS_ToString(ctx, val1); if (JS_IsException(val)) goto fail; } else { val = JS_DupValue(ctx, val1); } str = JS_VALUE_GET_STRING(val); len = str->len; if (!str->is_wide_char) { const uint8_t *src = str->u.str8; int count; /* count the number of non-ASCII characters */ /* Scanning the whole string is required for ASCII strings, and computing the number of non-ASCII bytes is less expensive than testing each byte, hence this method is faster for ASCII strings, which is the most common case. */ count = 0; for (pos = 0; pos < len; pos++) { count += src[pos] >> 7; } if (count == 0) { if (plen) *plen = len; return (const char *)src; } str_new = js_alloc_string(ctx, len + count, 0); if (!str_new) goto fail; q = str_new->u.str8; for (pos = 0; pos < len; pos++) { c = src[pos]; if (c < 0x80) { *q++ = c; } else { *q++ = (c >> 6) | 0xc0; *q++ = (c & 0x3f) | 0x80; } } } else { const uint16_t *src = str->u.str16; /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may produce 4 bytes but use 2 code points. */ str_new = js_alloc_string(ctx, len * 3, 0); if (!str_new) goto fail; q = str_new->u.str8; pos = 0; while (pos < len) { c = src[pos++]; if (c < 0x80) { *q++ = c; } else { if (is_hi_surrogate(c)) { if (pos < len && !cesu8) { c1 = src[pos]; if (is_lo_surrogate(c1)) { pos++; c = from_surrogate(c, c1); } else { /* Keep unmatched surrogate code points */ /* c = 0xfffd; */ /* error */ } } else { /* Keep unmatched surrogate code points */ /* c = 0xfffd; */ /* error */ } } q += unicode_to_utf8(q, c); } } } *q = '\0'; str_new->len = q - str_new->u.str8; JS_FreeValue(ctx, val); if (plen) *plen = str_new->len; return (const char *)str_new->u.str8; fail: if (plen) *plen = 0; return NULL; } void JS_FreeCString(JSContext *ctx, const char *ptr) { JSString *p; if (!ptr) return; /* purposely removing constness */ p = container_of(ptr, JSString, u); JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); } static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) { int c, i; for(i = 0; i < len; i++) { c = src1[i] - src2[i]; if (c != 0) return c; } return 0; } static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) { int c, i; for(i = 0; i < len; i++) { c = src1[i] - src2[i]; if (c != 0) return c; } return 0; } static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, int pos2, int len) { int res; if (likely(!p1->is_wide_char)) { if (likely(!p2->is_wide_char)) res = memcmp(p1->u.str8 + pos1, p2->u.str8 + pos2, len); else res = -memcmp16_8(p2->u.str16 + pos2, p1->u.str8 + pos1, len); } else { if (!p2->is_wide_char) res = memcmp16_8(p1->u.str16 + pos1, p2->u.str8 + pos2, len); else res = memcmp16(p1->u.str16 + pos1, p2->u.str16 + pos2, len); } return res; } /* return < 0, 0 or > 0 */ static int js_string_compare(JSContext *ctx, const JSString *p1, const JSString *p2) { int res, len; len = min_int(p1->len, p2->len); res = js_string_memcmp(p1, 0, p2, 0, len); if (res == 0) { if (p1->len == p2->len) res = 0; else if (p1->len < p2->len) res = -1; else res = 1; } return res; } static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len) { if (p->is_wide_char) { memcpy(dst, p->u.str16 + offset, len * 2); } else { const uint8_t *src1 = p->u.str8 + offset; int i; for(i = 0; i < len; i++) dst[i] = src1[i]; } } static JSValue JS_ConcatString1(JSContext *ctx, const JSString *p1, const JSString *p2) { JSString *p; uint32_t len; int is_wide_char; len = p1->len + p2->len; if (len > JS_STRING_LEN_MAX) return JS_ThrowInternalError(ctx, "string too long"); is_wide_char = p1->is_wide_char | p2->is_wide_char; p = js_alloc_string(ctx, len, is_wide_char); if (!p) return JS_EXCEPTION; if (!is_wide_char) { memcpy(p->u.str8, p1->u.str8, p1->len); memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len); p->u.str8[len] = '\0'; } else { copy_str16(p->u.str16, p1, 0, p1->len); copy_str16(p->u.str16 + p1->len, p2, 0, p2->len); } return JS_MKPTR(JS_TAG_STRING, p); } static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op2) { if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { JSString *p2 = JS_VALUE_GET_STRING(op2); size_t size1; if (p2->len == 0) return TRUE; if (p1->header.ref_count != 1) return FALSE; size1 = js_malloc_usable_size(ctx, p1); if (p1->is_wide_char) { if (size1 >= sizeof(*p1) + ((p1->len + p2->len) << 1)) { if (p2->is_wide_char) { memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); p1->len += p2->len; return TRUE; } else { size_t i; for (i = 0; i < p2->len; i++) { p1->u.str16[p1->len++] = p2->u.str8[i]; } return TRUE; } } } else if (!p2->is_wide_char) { if (size1 >= sizeof(*p1) + p1->len + p2->len + 1) { memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); p1->len += p2->len; p1->u.str8[p1->len] = '\0'; return TRUE; } } } return FALSE; } static JSValue JS_ConcatString2(JSContext *ctx, JSValue op1, JSValue op2) { JSValue ret; JSString *p1, *p2; p1 = JS_VALUE_GET_STRING(op1); if (JS_ConcatStringInPlace(ctx, p1, op2)) { JS_FreeValue(ctx, op2); return op1; } p2 = JS_VALUE_GET_STRING(op2); ret = JS_ConcatString1(ctx, p1, p2); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return ret; } /* Return the character at position 'idx'. 'val' must be a string or rope */ static int string_rope_get(JSValueConst val, uint32_t idx) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { return string_get(JS_VALUE_GET_STRING(val), idx); } else { JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); uint32_t len; if (JS_VALUE_GET_TAG(r->left) == JS_TAG_STRING) len = JS_VALUE_GET_STRING(r->left)->len; else len = JS_VALUE_GET_STRING_ROPE(r->left)->len; if (idx < len) return string_rope_get(r->left, idx); else return string_rope_get(r->right, idx - len); } } typedef struct { JSValueConst stack[JS_STRING_ROPE_MAX_DEPTH]; int stack_len; } JSStringRopeIter; static void string_rope_iter_init(JSStringRopeIter *s, JSValueConst val) { s->stack_len = 0; s->stack[s->stack_len++] = val; } /* iterate thru a rope and return the strings in order */ static JSString *string_rope_iter_next(JSStringRopeIter *s) { JSValueConst val; JSStringRope *r; if (s->stack_len == 0) return NULL; val = s->stack[--s->stack_len]; for(;;) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) return JS_VALUE_GET_STRING(val); r = JS_VALUE_GET_STRING_ROPE(val); assert(s->stack_len < JS_STRING_ROPE_MAX_DEPTH); s->stack[s->stack_len++] = r->right; val = r->left; } } static uint32_t string_rope_get_len(JSValueConst val) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) return JS_VALUE_GET_STRING(val)->len; else return JS_VALUE_GET_STRING_ROPE(val)->len; } static int js_string_rope_compare(JSContext *ctx, JSValueConst op1, JSValueConst op2, BOOL eq_only) { uint32_t len1, len2, len, pos1, pos2, l; int res; JSStringRopeIter it1, it2; JSString *p1, *p2; len1 = string_rope_get_len(op1); len2 = string_rope_get_len(op2); /* no need to go further for equality test if different length */ if (eq_only && len1 != len2) return 1; len = min_uint32(len1, len2); string_rope_iter_init(&it1, op1); string_rope_iter_init(&it2, op2); p1 = string_rope_iter_next(&it1); p2 = string_rope_iter_next(&it2); pos1 = 0; pos2 = 0; while (len != 0) { l = min_uint32(p1->len - pos1, p2->len - pos2); l = min_uint32(l, len); res = js_string_memcmp(p1, pos1, p2, pos2, l); if (res != 0) return res; len -= l; pos1 += l; if (pos1 >= p1->len) { p1 = string_rope_iter_next(&it1); pos1 = 0; } pos2 += l; if (pos2 >= p2->len) { p2 = string_rope_iter_next(&it2); pos2 = 0; } } if (len1 == len2) res = 0; else if (len1 < len2) res = -1; else res = 1; return res; } /* 'rope' must be a rope. return a string and modify the rope so that it won't need to be linearized again. */ static JSValue js_linearize_string_rope(JSContext *ctx, JSValue rope) { StringBuffer b_s, *b = &b_s; JSStringRope *r; JSValue ret; r = JS_VALUE_GET_STRING_ROPE(rope); /* check whether it is already linearized */ if (JS_VALUE_GET_TAG(r->right) == JS_TAG_STRING && JS_VALUE_GET_STRING(r->right)->len == 0) { ret = JS_DupValue(ctx, r->left); JS_FreeValue(ctx, rope); return ret; } if (string_buffer_init2(ctx, b, r->len, r->is_wide_char)) goto fail; if (string_buffer_concat_value(b, rope)) goto fail; ret = string_buffer_end(b); if (r->header.ref_count > 1) { /* update the rope so that it won't need to be linearized again */ JS_FreeValue(ctx, r->left); JS_FreeValue(ctx, r->right); r->left = JS_DupValue(ctx, ret); r->right = JS_AtomToString(ctx, JS_ATOM_empty_string); } JS_FreeValue(ctx, rope); return ret; fail: JS_FreeValue(ctx, rope); return JS_EXCEPTION; } static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope); /* op1 and op2 must be strings or string ropes */ static JSValue js_new_string_rope(JSContext *ctx, JSValue op1, JSValue op2) { uint32_t len; int is_wide_char, depth; JSStringRope *r; JSValue res; if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { JSString *p1 = JS_VALUE_GET_STRING(op1); len = p1->len; is_wide_char = p1->is_wide_char; depth = 0; } else { JSStringRope *r1 = JS_VALUE_GET_STRING_ROPE(op1); len = r1->len; is_wide_char = r1->is_wide_char; depth = r1->depth; } if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { JSString *p2 = JS_VALUE_GET_STRING(op2); len += p2->len; is_wide_char |= p2->is_wide_char; } else { JSStringRope *r2 = JS_VALUE_GET_STRING_ROPE(op2); len += r2->len; is_wide_char |= r2->is_wide_char; depth = max_int(depth, r2->depth); } if (len > JS_STRING_LEN_MAX) { JS_ThrowInternalError(ctx, "string too long"); goto fail; } r = js_malloc(ctx, sizeof(*r)); if (!r) goto fail; r->header.ref_count = 1; r->len = len; r->is_wide_char = is_wide_char; r->depth = depth + 1; r->left = op1; r->right = op2; res = JS_MKPTR(JS_TAG_STRING_ROPE, r); if (r->depth > JS_STRING_ROPE_MAX_DEPTH) { JSValue res2; #ifdef DUMP_ROPE_REBALANCE printf("rebalance: initial depth=%d\n", r->depth); #endif res2 = js_rebalancee_string_rope(ctx, res); #ifdef DUMP_ROPE_REBALANCE if (JS_VALUE_GET_TAG(res2) == JS_TAG_STRING_ROPE) printf("rebalance: final depth=%d\n", JS_VALUE_GET_STRING_ROPE(res2)->depth); #endif JS_FreeValue(ctx, res); return res2; } else { return res; } fail: JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return JS_EXCEPTION; } #define ROPE_N_BUCKETS 44 /* Fibonacii numbers starting from F_2 */ static const uint32_t rope_bucket_len[ROPE_N_BUCKETS] = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, /* > JS_STRING_LEN_MAX */ }; static int js_rebalancee_string_rope_rec(JSContext *ctx, JSValue *buckets, JSValueConst val) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { JSString *p = JS_VALUE_GET_STRING(val); uint32_t len, i; JSValue a, b; len = p->len; if (len == 0) return 0; /* nothing to do */ /* find the bucket i so that rope_bucket_len[i] <= len < rope_bucket_len[i + 1] and concatenate the ropes in the buckets before */ a = JS_NULL; i = 0; while (len >= rope_bucket_len[i + 1]) { b = buckets[i]; if (!JS_IsNull(b)) { buckets[i] = JS_NULL; if (JS_IsNull(a)) { a = b; } else { a = js_new_string_rope(ctx, b, a); if (JS_IsException(a)) return -1; } } i++; } if (!JS_IsNull(a)) { a = js_new_string_rope(ctx, a, JS_DupValue(ctx, val)); if (JS_IsException(a)) return -1; } else { a = JS_DupValue(ctx, val); } while (!JS_IsNull(buckets[i])) { a = js_new_string_rope(ctx, buckets[i], a); buckets[i] = JS_NULL; if (JS_IsException(a)) return -1; i++; } buckets[i] = a; } else { JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); js_rebalancee_string_rope_rec(ctx, buckets, r->left); js_rebalancee_string_rope_rec(ctx, buckets, r->right); } return 0; } /* Return a new rope which is balanced. Algorithm from "Ropes: an Alternative to Strings", Hans-J. Boehm, Russ Atkinson and Michael Plass. */ static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope) { JSValue buckets[ROPE_N_BUCKETS], a, b; int i; for(i = 0; i < ROPE_N_BUCKETS; i++) buckets[i] = JS_NULL; if (js_rebalancee_string_rope_rec(ctx, buckets, rope)) goto fail; a = JS_NULL; for(i = 0; i < ROPE_N_BUCKETS; i++) { b = buckets[i]; if (!JS_IsNull(b)) { buckets[i] = JS_NULL; if (JS_IsNull(a)) { a = b; } else { a = js_new_string_rope(ctx, b, a); if (JS_IsException(a)) goto fail; } } } /* fail safe */ if (JS_IsNull(a)) return JS_AtomToString(ctx, JS_ATOM_empty_string); else return a; fail: for(i = 0; i < ROPE_N_BUCKETS; i++) { JS_FreeValue(ctx, buckets[i]); } return JS_EXCEPTION; } /* op1 and op2 are converted to strings. For convenience, op1 or op2 = JS_EXCEPTION are accepted and return JS_EXCEPTION. */ static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) { JSString *p1, *p2; if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING && JS_VALUE_GET_TAG(op1) != JS_TAG_STRING_ROPE)) { op1 = JS_ToStringFree(ctx, op1); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); return JS_EXCEPTION; } } if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING && JS_VALUE_GET_TAG(op2) != JS_TAG_STRING_ROPE)) { op2 = JS_ToStringFree(ctx, op2); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); return JS_EXCEPTION; } } /* normal concatenation for short strings */ if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { p2 = JS_VALUE_GET_STRING(op2); if (p2->len == 0) { JS_FreeValue(ctx, op2); return op1; } if (p2->len <= JS_STRING_ROPE_SHORT_LEN) { if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { p1 = JS_VALUE_GET_STRING(op1); if (p1->len <= JS_STRING_ROPE_SHORT2_LEN) { return JS_ConcatString2(ctx, op1, op2); } else { return js_new_string_rope(ctx, op1, op2); } } else { JSStringRope *r1; r1 = JS_VALUE_GET_STRING_ROPE(op1); if (JS_VALUE_GET_TAG(r1->right) == JS_TAG_STRING && JS_VALUE_GET_STRING(r1->right)->len <= JS_STRING_ROPE_SHORT_LEN) { JSValue val, ret; val = JS_ConcatString2(ctx, JS_DupValue(ctx, r1->right), op2); if (JS_IsException(val)) { JS_FreeValue(ctx, op1); return JS_EXCEPTION; } ret = js_new_string_rope(ctx, JS_DupValue(ctx, r1->left), val); JS_FreeValue(ctx, op1); return ret; } } } } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { JSStringRope *r2; p1 = JS_VALUE_GET_STRING(op1); if (p1->len == 0) { JS_FreeValue(ctx, op1); return op2; } r2 = JS_VALUE_GET_STRING_ROPE(op2); if (JS_VALUE_GET_TAG(r2->left) == JS_TAG_STRING && JS_VALUE_GET_STRING(r2->left)->len <= JS_STRING_ROPE_SHORT_LEN) { JSValue val, ret; val = JS_ConcatString2(ctx, op1, JS_DupValue(ctx, r2->left)); if (JS_IsException(val)) { JS_FreeValue(ctx, op2); return JS_EXCEPTION; } ret = js_new_string_rope(ctx, val, JS_DupValue(ctx, r2->right)); JS_FreeValue(ctx, op2); return ret; } } return js_new_string_rope(ctx, op1, op2); } /* Shape support */ static inline size_t get_shape_size(size_t hash_size, size_t prop_size) { return hash_size * sizeof(uint32_t) + sizeof(JSShape) + prop_size * sizeof(JSShapeProperty); } static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) { return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); } static inline uint32_t *prop_hash_end(JSShape *sh) { return (uint32_t *)sh; } static inline void *get_alloc_from_shape(JSShape *sh) { return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); } static inline JSShapeProperty *get_shape_prop(JSShape *sh) { return sh->prop; } static int init_shape_hash(JSRuntime *rt) { rt->shape_hash_bits = 4; /* 16 shapes */ rt->shape_hash_size = 1 << rt->shape_hash_bits; rt->shape_hash_count = 0; rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * rt->shape_hash_size); if (!rt->shape_hash) return -1; return 0; } /* same magic hash multiplier as the Linux kernel */ static uint32_t shape_hash(uint32_t h, uint32_t val) { return (h + val) * 0x9e370001; } /* truncate the shape hash to 'hash_bits' bits */ static uint32_t get_shape_hash(uint32_t h, int hash_bits) { return h >> (32 - hash_bits); } static uint32_t shape_initial_hash(JSObject *proto) { uint32_t h; h = shape_hash(1, (uintptr_t)proto); if (sizeof(proto) > 4) h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); return h; } static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) { int new_shape_hash_size, i; uint32_t h; JSShape **new_shape_hash, *sh, *sh_next; new_shape_hash_size = 1 << new_shape_hash_bits; new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * new_shape_hash_size); if (!new_shape_hash) return -1; for(i = 0; i < rt->shape_hash_size; i++) { for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { sh_next = sh->shape_hash_next; h = get_shape_hash(sh->hash, new_shape_hash_bits); sh->shape_hash_next = new_shape_hash[h]; new_shape_hash[h] = sh; } } js_free_rt(rt, rt->shape_hash); rt->shape_hash_bits = new_shape_hash_bits; rt->shape_hash_size = new_shape_hash_size; rt->shape_hash = new_shape_hash; return 0; } static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) { uint32_t h; h = get_shape_hash(sh->hash, rt->shape_hash_bits); sh->shape_hash_next = rt->shape_hash[h]; rt->shape_hash[h] = sh; rt->shape_hash_count++; } static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) { uint32_t h; JSShape **psh; h = get_shape_hash(sh->hash, rt->shape_hash_bits); psh = &rt->shape_hash[h]; while (*psh != sh) psh = &(*psh)->shape_hash_next; *psh = sh->shape_hash_next; rt->shape_hash_count--; } /* create a new empty shape with prototype 'proto' */ static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, int hash_size, int prop_size) { JSRuntime *rt = ctx->rt; void *sh_alloc; JSShape *sh; /* resize the shape hash table if necessary */ if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { resize_shape_hash(rt, rt->shape_hash_bits + 1); } sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); if (!sh_alloc) return NULL; sh = get_shape_from_alloc(sh_alloc, hash_size); sh->header.ref_count = 1; add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); if (proto) JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto)); sh->proto = proto; memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * hash_size); sh->prop_hash_mask = hash_size - 1; sh->prop_size = prop_size; sh->prop_count = 0; sh->deleted_prop_count = 0; /* insert in the hash table */ sh->hash = shape_initial_hash(proto); sh->is_hashed = TRUE; sh->has_small_array_index = FALSE; js_shape_hash_link(ctx->rt, sh); return sh; } static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) { return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, JS_PROP_INITIAL_SIZE); } /* The shape is cloned. The new shape is not inserted in the shape hash table */ static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) { JSShape *sh; void *sh_alloc, *sh_alloc1; size_t size; JSShapeProperty *pr; uint32_t i, hash_size; hash_size = sh1->prop_hash_mask + 1; size = get_shape_size(hash_size, sh1->prop_size); sh_alloc = js_malloc(ctx, size); if (!sh_alloc) return NULL; sh_alloc1 = get_alloc_from_shape(sh1); memcpy(sh_alloc, sh_alloc1, size); sh = get_shape_from_alloc(sh_alloc, hash_size); sh->header.ref_count = 1; add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); sh->is_hashed = FALSE; if (sh->proto) { JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); } for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { JS_DupAtom(ctx, pr->atom); } return sh; } static JSShape *js_dup_shape(JSShape *sh) { sh->header.ref_count++; return sh; } static void js_free_shape0(JSRuntime *rt, JSShape *sh) { uint32_t i; JSShapeProperty *pr; assert(sh->header.ref_count == 0); if (sh->is_hashed) js_shape_hash_unlink(rt, sh); if (sh->proto != NULL) { JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); } pr = get_shape_prop(sh); for(i = 0; i < sh->prop_count; i++) { JS_FreeAtomRT(rt, pr->atom); pr++; } remove_gc_object(&sh->header); js_free_rt(rt, get_alloc_from_shape(sh)); } static void js_free_shape(JSRuntime *rt, JSShape *sh) { if (unlikely(--sh->header.ref_count <= 0)) { js_free_shape0(rt, sh); } } static void js_free_shape_null(JSRuntime *rt, JSShape *sh) { if (sh) js_free_shape(rt, sh); } /* make space to hold at least 'count' properties */ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, JSObject *p, uint32_t count) { JSShape *sh; uint32_t new_size, new_hash_size, new_hash_mask, i; JSShapeProperty *pr; void *sh_alloc; intptr_t h; JSShape *old_sh; sh = *psh; new_size = max_int(count, sh->prop_size * 3 / 2); /* Reallocate prop array first to avoid crash or size inconsistency in case of memory allocation failure */ if (p) { JSProperty *new_prop; new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); if (unlikely(!new_prop)) return -1; p->prop = new_prop; } new_hash_size = sh->prop_hash_mask + 1; while (new_hash_size < new_size) new_hash_size = 2 * new_hash_size; /* resize the property shapes. Using js_realloc() is not possible in case the GC runs during the allocation */ old_sh = sh; sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); if (!sh_alloc) return -1; sh = get_shape_from_alloc(sh_alloc, new_hash_size); list_del(&old_sh->header.link); /* copy all the shape properties */ memcpy(sh, old_sh, sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); if (new_hash_size != (sh->prop_hash_mask + 1)) { /* resize the hash table and the properties */ new_hash_mask = new_hash_size - 1; sh->prop_hash_mask = new_hash_mask; memset(prop_hash_end(sh) - new_hash_size, 0, sizeof(prop_hash_end(sh)[0]) * new_hash_size); for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { if (pr->atom != JS_ATOM_NULL) { h = ((uintptr_t)pr->atom & new_hash_mask); pr->hash_next = prop_hash_end(sh)[-h - 1]; prop_hash_end(sh)[-h - 1] = i + 1; } } } else { /* just copy the previous hash table */ memcpy(prop_hash_end(sh) - new_hash_size, prop_hash_end(old_sh) - new_hash_size, sizeof(prop_hash_end(sh)[0]) * new_hash_size); } js_free(ctx, get_alloc_from_shape(old_sh)); *psh = sh; sh->prop_size = new_size; return 0; } /* remove the deleted properties. */ static int compact_properties(JSContext *ctx, JSObject *p) { JSShape *sh, *old_sh; void *sh_alloc; intptr_t h; uint32_t new_hash_size, i, j, new_hash_mask, new_size; JSShapeProperty *old_pr, *pr; JSProperty *prop, *new_prop; sh = p->shape; assert(!sh->is_hashed); new_size = max_int(JS_PROP_INITIAL_SIZE, sh->prop_count - sh->deleted_prop_count); assert(new_size <= sh->prop_size); new_hash_size = sh->prop_hash_mask + 1; while ((new_hash_size / 2) >= new_size) new_hash_size = new_hash_size / 2; new_hash_mask = new_hash_size - 1; /* resize the hash table and the properties */ old_sh = sh; sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); if (!sh_alloc) return -1; sh = get_shape_from_alloc(sh_alloc, new_hash_size); list_del(&old_sh->header.link); memcpy(sh, old_sh, sizeof(JSShape)); list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); memset(prop_hash_end(sh) - new_hash_size, 0, sizeof(prop_hash_end(sh)[0]) * new_hash_size); j = 0; old_pr = old_sh->prop; pr = sh->prop; prop = p->prop; for(i = 0; i < sh->prop_count; i++) { if (old_pr->atom != JS_ATOM_NULL) { pr->atom = old_pr->atom; pr->flags = old_pr->flags; h = ((uintptr_t)old_pr->atom & new_hash_mask); pr->hash_next = prop_hash_end(sh)[-h - 1]; prop_hash_end(sh)[-h - 1] = j + 1; prop[j] = prop[i]; j++; pr++; } old_pr++; } assert(j == (sh->prop_count - sh->deleted_prop_count)); sh->prop_hash_mask = new_hash_mask; sh->prop_size = new_size; sh->deleted_prop_count = 0; sh->prop_count = j; p->shape = sh; js_free(ctx, get_alloc_from_shape(old_sh)); /* reduce the size of the object properties */ new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); if (new_prop) p->prop = new_prop; return 0; } static int add_shape_property(JSContext *ctx, JSShape **psh, JSObject *p, JSAtom atom, int prop_flags) { JSRuntime *rt = ctx->rt; JSShape *sh = *psh; JSShapeProperty *pr, *prop; uint32_t hash_mask, new_shape_hash = 0; intptr_t h; /* update the shape hash */ if (sh->is_hashed) { js_shape_hash_unlink(rt, sh); new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); } if (unlikely(sh->prop_count >= sh->prop_size)) { if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { /* in case of error, reinsert in the hash table. sh is still valid if resize_properties() failed */ if (sh->is_hashed) js_shape_hash_link(rt, sh); return -1; } sh = *psh; } if (sh->is_hashed) { sh->hash = new_shape_hash; js_shape_hash_link(rt, sh); } /* Initialize the new shape property. The object property at p->prop[sh->prop_count] is uninitialized */ prop = get_shape_prop(sh); pr = &prop[sh->prop_count++]; pr->atom = JS_DupAtom(ctx, atom); pr->flags = prop_flags; sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); /* add in hash table */ hash_mask = sh->prop_hash_mask; h = atom & hash_mask; pr->hash_next = prop_hash_end(sh)[-h - 1]; prop_hash_end(sh)[-h - 1] = sh->prop_count; return 0; } /* find a hashed empty shape matching the prototype. Return NULL if not found */ static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) { JSShape *sh1; uint32_t h, h1; h = shape_initial_hash(proto); h1 = get_shape_hash(h, rt->shape_hash_bits); for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { if (sh1->hash == h && sh1->proto == proto && sh1->prop_count == 0) { return sh1; } } return NULL; } /* find a hashed shape matching sh + (prop, prop_flags). Return NULL if not found */ static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, JSAtom atom, int prop_flags) { JSShape *sh1; uint32_t h, h1, i, n; h = sh->hash; h = shape_hash(h, atom); h = shape_hash(h, prop_flags); h1 = get_shape_hash(h, rt->shape_hash_bits); for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { /* we test the hash first so that the rest is done only if the shapes really match */ if (sh1->hash == h && sh1->proto == sh->proto && sh1->prop_count == ((n = sh->prop_count) + 1)) { for(i = 0; i < n; i++) { if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || unlikely(sh1->prop[i].flags != sh->prop[i].flags)) goto next; } if (unlikely(sh1->prop[n].atom != atom) || unlikely(sh1->prop[n].flags != prop_flags)) goto next; return sh1; } next: ; } return NULL; } static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) { char atom_buf[ATOM_GET_STR_BUF_SIZE]; int j; /* XXX: should output readable class prototype */ printf("%5d %3d%c %14p %5d %5d", i, sh->header.ref_count, " *"[sh->is_hashed], (void *)sh->proto, sh->prop_size, sh->prop_count); for(j = 0; j < sh->prop_count; j++) { printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), sh->prop[j].atom)); } printf("\n"); } static __maybe_unused void JS_DumpShapes(JSRuntime *rt) { int i; JSShape *sh; struct list_head *el; JSObject *p; JSGCObjectHeader *gp; printf("JSShapes: {\n"); printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); for(i = 0; i < rt->shape_hash_size; i++) { for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { JS_DumpShape(rt, i, sh); assert(sh->is_hashed); } } /* dump non-hashed shapes */ list_for_each(el, &rt->gc_obj_list) { gp = list_entry(el, JSGCObjectHeader, link); if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { p = (JSObject *)gp; if (!p->shape->is_hashed) { JS_DumpShape(rt, -1, p->shape); } } } printf("}\n"); } static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id) { JSObject *p; js_trigger_gc(ctx->rt, sizeof(JSObject)); p = js_malloc(ctx, sizeof(JSObject)); if (unlikely(!p)) goto fail; p->class_id = class_id; p->extensible = TRUE; p->free_mark = 0; p->is_exotic = 0; p->fast_array = 0; p->is_constructor = 0; p->has_immutable_prototype = 0; p->tmp_mark = 0; p->object_key_atom = JS_ATOM_NULL; p->u.opaque = NULL; p->shape = sh; p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); if (unlikely(!p->prop)) { js_free(ctx, p); fail: js_free_shape(ctx->rt, sh); return JS_EXCEPTION; } switch(class_id) { case JS_CLASS_OBJECT: break; case JS_CLASS_ARRAY: { JSProperty *pr; p->is_exotic = 1; p->fast_array = 1; p->u.array.u.values = NULL; p->u.array.count = 0; p->u.array.u1.size = 0; /* the length property is always the first one */ if (likely(sh == ctx->array_shape)) { pr = &p->prop[0]; } else { /* only used for the first array */ /* cannot fail */ pr = add_property(ctx, p, JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH); } pr->u.value = JS_NewInt32(ctx, 0); } break; case JS_CLASS_C_FUNCTION: p->prop[0].u.value = JS_NULL; break; case JS_CLASS_NUMBER: case JS_CLASS_STRING: case JS_CLASS_BOOLEAN: case JS_CLASS_SYMBOL: p->u.object_data = JS_NULL; goto set_exotic; case JS_CLASS_REGEXP: p->u.regexp.pattern = NULL; p->u.regexp.bytecode = NULL; goto set_exotic; default: set_exotic: if (ctx->rt->class_array[class_id].exotic) { p->is_exotic = 1; } break; } p->header.ref_count = 1; add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); return JS_MKPTR(JS_TAG_OBJECT, p); } static JSObject *get_proto_obj(JSValueConst proto_val) { if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) return NULL; else return JS_VALUE_GET_OBJ(proto_val); } /* WARNING: proto must be an object or JS_NULL */ JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val, JSClassID class_id) { JSShape *sh; JSObject *proto; proto = get_proto_obj(proto_val); sh = find_hashed_shape_proto(ctx->rt, proto); if (likely(sh)) { sh = js_dup_shape(sh); } else { sh = js_new_shape(ctx, proto); if (!sh) return JS_EXCEPTION; } return JS_NewObjectFromShape(ctx, sh, class_id); } static int JS_SetObjectData(JSContext *ctx, JSValueConst obj, JSValue val) { JSObject *p; if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ(obj); switch(p->class_id) { case JS_CLASS_NUMBER: case JS_CLASS_STRING: case JS_CLASS_BOOLEAN: case JS_CLASS_SYMBOL: JS_FreeValue(ctx, p->u.object_data); p->u.object_data = val; /* for JS_CLASS_STRING, 'val' must be JS_TAG_STRING (and not a rope) */ return 0; } } JS_FreeValue(ctx, val); if (!JS_IsException(obj)) JS_ThrowTypeError(ctx, "invalid object type"); return -1; } JSValue JS_NewObjectClass(JSContext *ctx, int class_id) { return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); } JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto) { return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); } JSValue JS_NewArray(JSContext *ctx) { return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), JS_CLASS_ARRAY); } JSValue JS_NewObject(JSContext *ctx) { /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); } static void js_function_set_properties(JSContext *ctx, JSValueConst func_obj, JSAtom name, int len) { /* ES6 feature non compatible with ES5.1: length is configurable */ JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, JS_NewInt32(ctx, len), JS_PROP_CONFIGURABLE); JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); } static BOOL js_class_has_bytecode(JSClassID class_id) { return class_id == JS_CLASS_BYTECODE_FUNCTION; } /* return NULL without exception if not a function or no bytecode */ static JSFunctionBytecode *JS_GetFunctionBytecode(JSValueConst val) { JSObject *p; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return NULL; p = JS_VALUE_GET_OBJ(val); if (!js_class_has_bytecode(p->class_id)) return NULL; return p->u.func.function_bytecode; } static JSValue js_get_function_name(JSContext *ctx, JSAtom name) { JSValue name_str; name_str = JS_AtomToString(ctx, name); if (JS_AtomSymbolHasDescription(ctx, name)) { name_str = JS_ConcatString3(ctx, "[", name_str, "]"); } return name_str; } /* Modify the name of a method according to the atom and 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and JS_PROP_HAS_SET. Also set the home object of the method. Return < 0 if exception. */ static int js_method_set_properties(JSContext *ctx, JSValueConst func_obj, JSAtom name, int flags, JSValueConst home_obj) { JSValue name_str; name_str = js_get_function_name(ctx, name); if (flags & JS_PROP_HAS_GET) { name_str = JS_ConcatString3(ctx, "get ", name_str, ""); } else if (flags & JS_PROP_HAS_SET) { name_str = JS_ConcatString3(ctx, "set ", name_str, ""); } if (JS_IsException(name_str)) return -1; if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, JS_PROP_CONFIGURABLE) < 0) return -1; return 0; } /* Note: at least 'length' arguments will be readable in 'argv' */ static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic, JSValueConst proto_val) { JSValue func_obj; JSObject *p; JSAtom name_atom; func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); if (JS_IsException(func_obj)) return func_obj; p = JS_VALUE_GET_OBJ(func_obj); p->u.cfunc.realm = JS_DupContext(ctx); p->u.cfunc.c_function.generic = func; p->u.cfunc.length = length; p->u.cfunc.cproto = cproto; p->u.cfunc.magic = magic; p->is_constructor = (cproto == JS_CFUNC_constructor || cproto == JS_CFUNC_constructor_magic || cproto == JS_CFUNC_constructor_or_func || cproto == JS_CFUNC_constructor_or_func_magic); if (!name) name = ""; name_atom = JS_NewAtom(ctx, name); if (name_atom == JS_ATOM_NULL) { JS_FreeValue(ctx, func_obj); return JS_EXCEPTION; } js_function_set_properties(ctx, func_obj, name_atom, length); JS_FreeAtom(ctx, name_atom); return func_obj; } /* Note: at least 'length' arguments will be readable in 'argv' */ JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { return JS_NewCFunction3(ctx, func, name, length, cproto, magic, ctx->function_proto); } typedef struct JSCFunctionDataRecord { JSCFunctionData *func; uint8_t length; uint8_t data_len; uint16_t magic; JSValue data[0]; } JSCFunctionDataRecord; static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val) { JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); int i; if (s) { for(i = 0; i < s->data_len; i++) { JS_FreeValueRT(rt, s->data[i]); } js_free_rt(rt, s); } } static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); int i; if (s) { for(i = 0; i < s->data_len; i++) { JS_MarkValue(rt, s->data[i], mark_func); } } } static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst *argv, int flags) { JSCFunctionDataRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); JSValueConst *arg_buf; int i; /* XXX: could add the function on the stack for debug */ if (unlikely(argc < s->length)) { arg_buf = alloca(sizeof(arg_buf[0]) * s->length); for(i = 0; i < argc; i++) arg_buf[i] = argv[i]; for(i = argc; i < s->length; i++) arg_buf[i] = JS_NULL; } else { arg_buf = argv; } return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data); } JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, int length, int magic, int data_len, JSValueConst *data) { JSCFunctionDataRecord *s; JSValue func_obj; int i; func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, JS_CLASS_C_FUNCTION_DATA); if (JS_IsException(func_obj)) return func_obj; s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); if (!s) { JS_FreeValue(ctx, func_obj); return JS_EXCEPTION; } s->func = func; s->length = length; s->data_len = data_len; s->magic = magic; for(i = 0; i < data_len; i++) s->data[i] = JS_DupValue(ctx, data[i]); JS_SetOpaque(func_obj, s); js_function_set_properties(ctx, func_obj, JS_ATOM_empty_string, length); return func_obj; } static JSContext *js_autoinit_get_realm(JSProperty *pr) { return (JSContext *)(pr->u.init.realm_and_id & ~3); } static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) { return pr->u.init.realm_and_id & 3; } static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) { JS_FreeContext(js_autoinit_get_realm(pr)); } static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, JS_MarkFunc *mark_func) { mark_func(rt, &js_autoinit_get_realm(pr)->header); } static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) { if (unlikely(prop_flags & JS_PROP_TMASK)) { if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { if (pr->u.getset.getter) JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); if (pr->u.getset.setter) JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { free_var_ref(rt, pr->u.var_ref); } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { js_autoinit_free(rt, pr); } } else { JS_FreeValueRT(rt, pr->u.value); } } static force_inline JSShapeProperty *find_own_property1(JSObject *p, JSAtom atom) { JSShape *sh; JSShapeProperty *pr, *prop; intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; h = prop_hash_end(sh)[-h - 1]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; if (likely(pr->atom == atom)) { return pr; } h = pr->hash_next; } return NULL; } static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, JSObject *p, JSAtom atom) { JSShape *sh; JSShapeProperty *pr, *prop; intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; h = prop_hash_end(sh)[-h - 1]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; if (likely(pr->atom == atom)) { *ppr = &p->prop[h - 1]; /* the compiler should be able to assume that pr != NULL here */ return pr; } h = pr->hash_next; } *ppr = NULL; return NULL; } /* indicate that the object may be part of a function prototype cycle */ static void set_cycle_flag(JSContext *ctx, JSValueConst obj) { } static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) { if (var_ref) { assert(var_ref->header.ref_count > 0); if (--var_ref->header.ref_count == 0) { if (var_ref->is_detached) { JS_FreeValueRT(rt, var_ref->value); } else { list_del(&var_ref->var_ref_link); /* still on the stack */ } remove_gc_object(&var_ref->header); js_free_rt(rt, var_ref); } } } static void js_array_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); int i; for(i = 0; i < p->u.array.count; i++) { JS_FreeValueRT(rt, p->u.array.u.values[i]); } js_free_rt(rt, p->u.array.u.values); } static void js_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); int i; for(i = 0; i < p->u.array.count; i++) { JS_MarkValue(rt, p->u.array.u.values[i], mark_func); } } static void js_object_data_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JS_FreeValueRT(rt, p->u.object_data); p->u.object_data = JS_NULL; } static void js_object_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JS_MarkValue(rt, p->u.object_data, mark_func); } static void js_c_function_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); if (p->u.cfunc.realm) JS_FreeContext(p->u.cfunc.realm); } static void js_c_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); if (p->u.cfunc.realm) mark_func(rt, &p->u.cfunc.realm->header); } static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSFunctionBytecode *b; JSVarRef **var_refs; int i; b = p->u.func.function_bytecode; if (b) { var_refs = p->u.func.var_refs; if (var_refs) { for(i = 0; i < b->closure_var_count; i++) free_var_ref(rt, var_refs[i]); js_free_rt(rt, var_refs); } JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); } } static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSVarRef **var_refs = p->u.func.var_refs; JSFunctionBytecode *b = p->u.func.function_bytecode; int i; if (b) { if (var_refs) { for(i = 0; i < b->closure_var_count; i++) { JSVarRef *var_ref = var_refs[i]; if (var_ref) { mark_func(rt, &var_ref->header); } } } /* must mark the function bytecode because template objects may be part of a cycle */ JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); } } static void js_bound_function_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSBoundFunction *bf = p->u.bound_function; int i; JS_FreeValueRT(rt, bf->func_obj); JS_FreeValueRT(rt, bf->this_val); for(i = 0; i < bf->argc; i++) { JS_FreeValueRT(rt, bf->argv[i]); } js_free_rt(rt, bf); } static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSBoundFunction *bf = p->u.bound_function; int i; JS_MarkValue(rt, bf->func_obj, mark_func); JS_MarkValue(rt, bf->this_val, mark_func); for(i = 0; i < bf->argc; i++) JS_MarkValue(rt, bf->argv[i], mark_func); } static void free_object(JSRuntime *rt, JSObject *p) { int i; JSClassFinalizer *finalizer; JSShape *sh; JSShapeProperty *pr; p->free_mark = 1; /* used to tell the object is invalid when freeing cycles */ /* free all the fields */ sh = p->shape; pr = get_shape_prop(sh); for(i = 0; i < sh->prop_count; i++) { free_property(rt, &p->prop[i], pr->flags); pr++; } js_free_rt(rt, p->prop); /* as an optimization we destroy the shape immediately without putting it in gc_zero_ref_count_list */ js_free_shape(rt, sh); /* fail safe */ p->shape = NULL; p->prop = NULL; finalizer = rt->class_array[p->class_id].finalizer; if (finalizer) (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); /* fail safe */ p->class_id = 0; p->u.opaque = NULL; p->u.func.var_refs = NULL; remove_gc_object(&p->header); /* no more weakrefs: free if no strong refs, else queue for zero-ref processing */ if (p->header.ref_count == 0) js_free_rt(rt, p); else list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); } static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) { switch(gp->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: free_object(rt, (JSObject *)gp); break; case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: free_function_bytecode(rt, (JSFunctionBytecode *)gp); break; default: abort(); } } static void free_zero_refcount(JSRuntime *rt) { struct list_head *el; JSGCObjectHeader *p; rt->gc_phase = JS_GC_PHASE_DECREF; for(;;) { el = rt->gc_zero_ref_count_list.next; if (el == &rt->gc_zero_ref_count_list) break; p = list_entry(el, JSGCObjectHeader, link); assert(p->ref_count == 0); free_gc_object(rt, p); } rt->gc_phase = JS_GC_PHASE_NONE; } /* called with the ref_count of 'v' reaches zero. */ void __JS_FreeValueRT(JSRuntime *rt, JSValue v) { uint32_t tag = JS_VALUE_GET_TAG(v); #ifdef DUMP_FREE { printf("Freeing "); if (tag == JS_TAG_OBJECT) { JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); } else { JS_DumpValueShort(rt, v); printf("\n"); } } #endif switch(tag) { case JS_TAG_STRING: { JSString *p = JS_VALUE_GET_STRING(v); if (p->atom_type) { JS_FreeAtomStruct(rt, p); } else { #ifdef DUMP_LEAKS list_del(&p->link); #endif js_free_rt(rt, p); } } break; case JS_TAG_STRING_ROPE: /* Note: recursion is acceptable because the rope depth is bounded */ { JSStringRope *p = JS_VALUE_GET_STRING_ROPE(v); JS_FreeValueRT(rt, p->left); JS_FreeValueRT(rt, p->right); js_free_rt(rt, p); } break; case JS_TAG_OBJECT: case JS_TAG_FUNCTION_BYTECODE: { JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { list_del(&p->link); list_add(&p->link, &rt->gc_zero_ref_count_list); p->mark = 1; /* indicate that the object is about to be freed */ if (rt->gc_phase == JS_GC_PHASE_NONE) { free_zero_refcount(rt); } } } break; case JS_TAG_SYMBOL: { JSAtomStruct *p = JS_VALUE_GET_PTR(v); JS_FreeAtomStruct(rt, p); } break; default: abort(); } } void __JS_FreeValue(JSContext *ctx, JSValue v) { __JS_FreeValueRT(ctx->rt, v); } /* garbage collection */ static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, JSGCObjectTypeEnum type) { h->mark = 0; h->gc_obj_type = type; list_add_tail(&h->link, &rt->gc_obj_list); } static void remove_gc_object(JSGCObjectHeader *h) { list_del(&h->link); } void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { if (JS_VALUE_HAS_REF_COUNT(val)) { switch(JS_VALUE_GET_TAG(val)) { case JS_TAG_OBJECT: case JS_TAG_FUNCTION_BYTECODE: mark_func(rt, JS_VALUE_GET_PTR(val)); break; default: break; } } } static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, JS_MarkFunc *mark_func) { switch(gp->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: { JSObject *p = (JSObject *)gp; JSShapeProperty *prs; JSShape *sh; int i; sh = p->shape; mark_func(rt, &sh->header); /* mark all the fields */ prs = get_shape_prop(sh); for(i = 0; i < sh->prop_count; i++) { JSProperty *pr = &p->prop[i]; if (prs->atom != JS_ATOM_NULL) { if (prs->flags & JS_PROP_TMASK) { if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { if (pr->u.getset.getter) mark_func(rt, &pr->u.getset.getter->header); if (pr->u.getset.setter) mark_func(rt, &pr->u.getset.setter->header); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { /* Note: the tag does not matter provided it is a GC object */ mark_func(rt, &pr->u.var_ref->header); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { js_autoinit_mark(rt, pr, mark_func); } } else { JS_MarkValue(rt, pr->u.value, mark_func); } } prs++; } if (p->class_id != JS_CLASS_OBJECT) { JSClassGCMark *gc_mark; gc_mark = rt->class_array[p->class_id].gc_mark; if (gc_mark) gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); } } break; case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: /* the template objects can be part of a cycle */ { JSFunctionBytecode *b = (JSFunctionBytecode *)gp; int i; for(i = 0; i < b->cpool_count; i++) { JS_MarkValue(rt, b->cpool[i], mark_func); } if (b->realm) mark_func(rt, &b->realm->header); } break; case JS_GC_OBJ_TYPE_VAR_REF: { JSVarRef *var_ref = (JSVarRef *)gp; if (var_ref->is_detached) { JS_MarkValue(rt, *var_ref->pvalue, mark_func); } } break; case JS_GC_OBJ_TYPE_SHAPE: { JSShape *sh = (JSShape *)gp; if (sh->proto != NULL) { mark_func(rt, &sh->proto->header); } } break; case JS_GC_OBJ_TYPE_JS_CONTEXT: { JSContext *ctx = (JSContext *)gp; JS_MarkContext(rt, ctx, mark_func); } break; default: abort(); } } static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) { assert(p->ref_count > 0); p->ref_count--; if (p->ref_count == 0 && p->mark == 1) { list_del(&p->link); list_add_tail(&p->link, &rt->tmp_obj_list); } } static void gc_decref(JSRuntime *rt) { struct list_head *el, *el1; JSGCObjectHeader *p; init_list_head(&rt->tmp_obj_list); /* decrement the refcount of all the children of all the GC objects and move the GC objects with zero refcount to tmp_obj_list */ list_for_each_safe(el, el1, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); assert(p->mark == 0); mark_children(rt, p, gc_decref_child); p->mark = 1; if (p->ref_count == 0) { list_del(&p->link); list_add_tail(&p->link, &rt->tmp_obj_list); } } } static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) { p->ref_count++; if (p->ref_count == 1) { /* ref_count was 0: remove from tmp_obj_list and add at the end of gc_obj_list */ list_del(&p->link); list_add_tail(&p->link, &rt->gc_obj_list); p->mark = 0; /* reset the mark for the next GC call */ } } static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) { p->ref_count++; } static void gc_scan(JSRuntime *rt) { struct list_head *el; JSGCObjectHeader *p; /* keep the objects with a refcount > 0 and their children. */ list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); assert(p->ref_count > 0); p->mark = 0; /* reset the mark for the next GC call */ mark_children(rt, p, gc_scan_incref_child); } /* restore the refcount of the objects to be deleted. */ list_for_each(el, &rt->tmp_obj_list) { p = list_entry(el, JSGCObjectHeader, link); mark_children(rt, p, gc_scan_incref_child2); } } static void gc_free_cycles(JSRuntime *rt) { struct list_head *el, *el1; JSGCObjectHeader *p; #ifdef DUMP_GC_FREE BOOL header_done = FALSE; #endif rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; for(;;) { el = rt->tmp_obj_list.next; if (el == &rt->tmp_obj_list) break; p = list_entry(el, JSGCObjectHeader, link); /* Only need to free the GC object associated with JS values or async functions. The rest will be automatically removed because they must be referenced by them. */ switch(p->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: #ifdef DUMP_GC_FREE if (!header_done) { printf("Freeing cycles:\n"); JS_DumpObjectHeader(rt); header_done = TRUE; } JS_DumpGCObject(rt, p); #endif free_gc_object(rt, p); break; default: list_del(&p->link); list_add_tail(&p->link, &rt->gc_zero_ref_count_list); break; } } rt->gc_phase = JS_GC_PHASE_NONE; list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { p = list_entry(el, JSGCObjectHeader, link); assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); js_free_rt(rt, p); } init_list_head(&rt->gc_zero_ref_count_list); } static void JS_RunGCInternal(JSRuntime *rt) { /* decrement the reference of the children of each object. mark = 1 after this pass. */ gc_decref(rt); /* keep the GC objects with a non zero refcount and their childs */ gc_scan(rt); /* free the GC objects in a cycle */ gc_free_cycles(rt); } void JS_RunGC(JSRuntime *rt) { JS_RunGCInternal(rt); } /* Return false if not an object or if the object has already been freed (zombie objects are visible in finalizers when freeing cycles). */ BOOL JS_IsLiveObject(JSRuntime *rt, JSValueConst obj) { JSObject *p; if (!JS_IsObject(obj)) return FALSE; p = JS_VALUE_GET_OBJ(obj); return !p->free_mark; } /* Compute memory used by various object types */ /* XXX: poor man's approach to handling multiply referenced objects */ typedef struct JSMemoryUsage_helper { double memory_used_count; double str_count; double str_size; int64_t js_func_count; double js_func_size; int64_t js_func_code_size; int64_t js_func_pc2line_count; int64_t js_func_pc2line_size; } JSMemoryUsage_helper; static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp); static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) { if (!str->atom_type) { /* atoms are handled separately */ double s_ref_count = str->header.ref_count; hp->str_count += 1 / s_ref_count; hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + 1 - str->is_wide_char) / s_ref_count); } } static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) { int memory_used_count, js_func_size, i; memory_used_count = 0; js_func_size = offsetof(JSFunctionBytecode, debug); if (b->vardefs) { js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); } if (b->cpool) { js_func_size += b->cpool_count * sizeof(*b->cpool); for (i = 0; i < b->cpool_count; i++) { JSValueConst val = b->cpool[i]; compute_value_size(val, hp); } } if (b->closure_var) { js_func_size += b->closure_var_count * sizeof(*b->closure_var); } if (!b->read_only_bytecode && b->byte_code_buf) { hp->js_func_code_size += b->byte_code_len; } if (b->has_debug) { js_func_size += sizeof(*b) - offsetof(JSFunctionBytecode, debug); if (b->debug.source) { memory_used_count++; js_func_size += b->debug.source_len + 1; } if (b->debug.pc2line_len) { memory_used_count++; hp->js_func_pc2line_count += 1; hp->js_func_pc2line_size += b->debug.pc2line_len; } } hp->js_func_size += js_func_size; hp->js_func_count += 1; hp->memory_used_count += memory_used_count; } static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp) { switch(JS_VALUE_GET_TAG(val)) { case JS_TAG_STRING: compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); break; } } void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) { struct list_head *el; int i; JSMemoryUsage_helper mem = { 0 }, *hp = &mem; memset(s, 0, sizeof(*s)); s->malloc_count = rt->malloc_state.malloc_count; s->malloc_size = rt->malloc_state.malloc_size; s->malloc_limit = rt->malloc_state.malloc_limit; s->memory_used_count = 2; /* rt + rt->class_array */ s->memory_used_size = sizeof(JSRuntime) + sizeof(JSValue) * rt->class_count; list_for_each(el, &rt->context_list) { JSContext *ctx = list_entry(el, JSContext, link); JSShape *sh = ctx->array_shape; s->memory_used_count += 2; /* ctx + ctx->class_proto */ s->memory_used_size += sizeof(JSContext) + sizeof(JSValue) * rt->class_count; s->binary_object_count += ctx->binary_object_count; s->binary_object_size += ctx->binary_object_size; /* the hashed shapes are counted separately */ if (sh && !sh->is_hashed) { int hash_size = sh->prop_hash_mask + 1; s->shape_count++; s->shape_size += get_shape_size(hash_size, sh->prop_size); } } list_for_each(el, &rt->gc_obj_list) { JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); JSObject *p; JSShape *sh; JSShapeProperty *prs; /* XXX: could count the other GC object types too */ if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { compute_bytecode_size((JSFunctionBytecode *)gp, hp); continue; } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { continue; } p = (JSObject *)gp; sh = p->shape; s->obj_count++; if (p->prop) { s->memory_used_count++; s->prop_size += sh->prop_size * sizeof(*p->prop); s->prop_count += sh->prop_count; prs = get_shape_prop(sh); for(i = 0; i < sh->prop_count; i++) { JSProperty *pr = &p->prop[i]; if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { compute_value_size(pr->u.value, hp); } prs++; } } /* the hashed shapes are counted separately */ if (!sh->is_hashed) { int hash_size = sh->prop_hash_mask + 1; s->shape_count++; s->shape_size += get_shape_size(hash_size, sh->prop_size); } switch(p->class_id) { case JS_CLASS_ARRAY: /* u.array | length */ s->array_count++; if (p->fast_array) { s->fast_array_count++; if (p->u.array.u.values) { s->memory_used_count++; s->memory_used_size += p->u.array.count * sizeof(*p->u.array.u.values); s->fast_array_elements += p->u.array.count; for (i = 0; i < p->u.array.count; i++) { compute_value_size(p->u.array.u.values[i], hp); } } } break; case JS_CLASS_NUMBER: /* u.object_data */ case JS_CLASS_STRING: /* u.object_data */ case JS_CLASS_BOOLEAN: /* u.object_data */ case JS_CLASS_SYMBOL: /* u.object_data */ compute_value_size(p->u.object_data, hp); break; case JS_CLASS_C_FUNCTION: /* u.cfunc */ s->c_func_count++; break; case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ { JSFunctionBytecode *b = p->u.func.function_bytecode; JSVarRef **var_refs = p->u.func.var_refs; /* home_object: object will be accounted for in list scan */ if (var_refs) { s->memory_used_count++; s->js_func_size += b->closure_var_count * sizeof(*var_refs); for (i = 0; i < b->closure_var_count; i++) { if (var_refs[i]) { double ref_count = var_refs[i]->header.ref_count; s->memory_used_count += 1 / ref_count; s->js_func_size += sizeof(*var_refs[i]) / ref_count; /* handle non object closed values */ if (var_refs[i]->pvalue == &var_refs[i]->value) { /* potential multiple count */ compute_value_size(var_refs[i]->value, hp); } } } } } break; case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ { JSBoundFunction *bf = p->u.bound_function; /* func_obj and this_val are objects */ for (i = 0; i < bf->argc; i++) { compute_value_size(bf->argv[i], hp); } s->memory_used_count += 1; s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); } break; case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ { JSCFunctionDataRecord *fd = p->u.c_function_data_record; if (fd) { for (i = 0; i < fd->data_len; i++) { compute_value_size(fd->data[i], hp); } s->memory_used_count += 1; s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); } } break; case JS_CLASS_REGEXP: /* u.regexp */ compute_jsstring_size(p->u.regexp.pattern, hp); compute_jsstring_size(p->u.regexp.bytecode, hp); break; default: /* XXX: class definition should have an opaque block size */ if (p->u.opaque) { s->memory_used_count += 1; } break; } } s->obj_size += s->obj_count * sizeof(JSObject); /* hashed shapes */ s->memory_used_count++; /* rt->shape_hash */ s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; for(i = 0; i < rt->shape_hash_size; i++) { JSShape *sh; for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { int hash_size = sh->prop_hash_mask + 1; s->shape_count++; s->shape_size += get_shape_size(hash_size, sh->prop_size); } } /* atoms */ s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ s->atom_count = rt->atom_count; s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + sizeof(rt->atom_hash[0]) * rt->atom_hash_size; for(i = 0; i < rt->atom_size; i++) { JSAtomStruct *p = rt->atom_array[i]; if (!atom_is_free(p)) { s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + 1 - p->is_wide_char); } } s->str_count = round(mem.str_count); s->str_size = round(mem.str_size); s->js_func_count = mem.js_func_count; s->js_func_size = round(mem.js_func_size); s->js_func_code_size = mem.js_func_code_size; s->js_func_pc2line_count = mem.js_func_pc2line_count; s->js_func_pc2line_size = mem.js_func_pc2line_size; s->memory_used_count += round(mem.memory_used_count) + s->atom_count + s->str_count + s->obj_count + s->shape_count + s->js_func_count + s->js_func_pc2line_count; s->memory_used_size += s->atom_size + s->str_size + s->obj_size + s->prop_size + s->shape_size + s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; } void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) { // fprintf(fp, "QuickJS memory usage -- " CONFIG_VERSION " version, %d-bit, malloc limit: %"PRId64"\n\n", // (int)sizeof(void *) * 8, s->malloc_limit); #if 1 if (rt) { static const struct { const char *name; size_t size; } object_types[] = { { "JSRuntime", sizeof(JSRuntime) }, { "JSContext", sizeof(JSContext) }, { "JSObject", sizeof(JSObject) }, { "JSString", sizeof(JSString) }, { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, }; int i, usage_size_ok = 0; for(i = 0; i < countof(object_types); i++) { unsigned int size = object_types[i].size; void *p = js_malloc_rt(rt, size); if (p) { unsigned int size1 = js_malloc_usable_size_rt(rt, p); if (size1 >= size) { usage_size_ok = 1; fprintf(fp, " %3u + %-2u %s\n", size, size1 - size, object_types[i].name); } js_free_rt(rt, p); } } if (!usage_size_ok) { fprintf(fp, " malloc_usable_size unavailable\n"); } { int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; int class_id; struct list_head *el; list_for_each(el, &rt->gc_obj_list) { JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); JSObject *p; if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { p = (JSObject *)gp; obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; } } fprintf(fp, "\n" "JSObject classes\n"); if (obj_classes[0]) fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { if (obj_classes[class_id] && class_id < rt->class_count) { char buf[ATOM_GET_STR_BUF_SIZE]; fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, JS_AtomGetStrRT(rt, buf, sizeof(buf), rt->class_array[class_id].class_name)); } } if (obj_classes[JS_CLASS_INIT_COUNT]) fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); } fprintf(fp, "\n"); } #endif fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); if (s->malloc_count) { fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", "memory allocated", s->malloc_count, s->malloc_size, (double)s->malloc_size / s->malloc_count); fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", "memory used", s->memory_used_count, s->memory_used_size, MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / s->memory_used_count)); } if (s->atom_count) { fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", "atoms", s->atom_count, s->atom_size, (double)s->atom_size / s->atom_count); } if (s->str_count) { fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", "strings", s->str_count, s->str_size, (double)s->str_size / s->str_count); } if (s->obj_count) { fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", "objects", s->obj_count, s->obj_size, (double)s->obj_size / s->obj_count); fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", " properties", s->prop_count, s->prop_size, (double)s->prop_count / s->obj_count); fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", " shapes", s->shape_count, s->shape_size, (double)s->shape_size / s->shape_count); } if (s->js_func_count) { fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", "bytecode functions", s->js_func_count, s->js_func_size); fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", " bytecode", s->js_func_count, s->js_func_code_size, (double)s->js_func_code_size / s->js_func_count); if (s->js_func_pc2line_count) { fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", " pc2line", s->js_func_pc2line_count, s->js_func_pc2line_size, (double)s->js_func_pc2line_size / s->js_func_pc2line_count); } } if (s->c_func_count) { fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); } if (s->array_count) { fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); if (s->fast_array_count) { fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", " elements", s->fast_array_elements, s->fast_array_elements * (int)sizeof(JSValue), (double)s->fast_array_elements / s->fast_array_count); } } if (s->binary_object_count) { fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", "binary objects", s->binary_object_count, s->binary_object_size); } } JSValue JS_GetGlobalObject(JSContext *ctx) { return JS_DupValue(ctx, ctx->global_obj); } /* WARNING: obj is freed */ JSValue JS_Throw(JSContext *ctx, JSValue obj) { JSRuntime *rt = ctx->rt; JS_FreeValue(ctx, rt->current_exception); rt->current_exception = obj; rt->current_exception_is_uncatchable = FALSE; return JS_EXCEPTION; } /* return the pending exception (cannot be called twice). */ JSValue JS_GetException(JSContext *ctx) { JSValue val; JSRuntime *rt = ctx->rt; val = rt->current_exception; rt->current_exception = JS_UNINITIALIZED; return val; } JS_BOOL JS_HasException(JSContext *ctx) { return !JS_IsUninitialized(ctx->rt->current_exception); } static void dbuf_put_leb128(DynBuf *s, uint32_t v) { uint32_t a; for(;;) { a = v & 0x7f; v >>= 7; if (v != 0) { dbuf_putc(s, a | 0x80); } else { dbuf_putc(s, a); break; } } } static void dbuf_put_sleb128(DynBuf *s, int32_t v1) { uint32_t v = v1; dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); } static int get_leb128(uint32_t *pval, const uint8_t *buf, const uint8_t *buf_end) { const uint8_t *ptr = buf; uint32_t v, a, i; v = 0; for(i = 0; i < 5; i++) { if (unlikely(ptr >= buf_end)) break; a = *ptr++; v |= (a & 0x7f) << (i * 7); if (!(a & 0x80)) { *pval = v; return ptr - buf; } } *pval = 0; return -1; } static int get_sleb128(int32_t *pval, const uint8_t *buf, const uint8_t *buf_end) { int ret; uint32_t val; ret = get_leb128(&val, buf, buf_end); if (ret < 0) { *pval = 0; return -1; } *pval = (val >> 1) ^ -(val & 1); return ret; } /* use pc_value = -1 to get the position of the function definition */ static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, uint32_t pc_value, int *pcol_num) { const uint8_t *p_end, *p; int new_line_num, line_num, pc, v, ret, new_col_num, col_num; uint32_t val; unsigned int op; if (!b->has_debug || !b->debug.pc2line_buf) goto fail; /* function was stripped */ p = b->debug.pc2line_buf; p_end = p + b->debug.pc2line_len; /* get the function line and column numbers */ ret = get_leb128(&val, p, p_end); if (ret < 0) goto fail; p += ret; line_num = val + 1; ret = get_leb128(&val, p, p_end); if (ret < 0) goto fail; p += ret; col_num = val + 1; if (pc_value != -1) { pc = 0; while (p < p_end) { op = *p++; if (op == 0) { ret = get_leb128(&val, p, p_end); if (ret < 0) goto fail; pc += val; p += ret; ret = get_sleb128(&v, p, p_end); if (ret < 0) goto fail; p += ret; new_line_num = line_num + v; } else { op -= PC2LINE_OP_FIRST; pc += (op / PC2LINE_RANGE); new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; } ret = get_sleb128(&v, p, p_end); if (ret < 0) goto fail; p += ret; new_col_num = col_num + v; if (pc_value < pc) goto done; line_num = new_line_num; col_num = new_col_num; } } done: *pcol_num = col_num; return line_num; fail: *pcol_num = 0; return 0; } /* return a string property without executing arbitrary JS code (used when dumping the stack trace or in debug print). */ static const char *get_prop_string(JSContext *ctx, JSValueConst obj, JSAtom prop) { JSObject *p; JSProperty *pr; JSShapeProperty *prs; JSValueConst val; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return NULL; p = JS_VALUE_GET_OBJ(obj); prs = find_own_property(&pr, p, prop); if (!prs) { /* we look at one level in the prototype to handle the 'name' field of the Error objects */ p = p->shape->proto; if (!p) return NULL; prs = find_own_property(&pr, p, prop); if (!prs) return NULL; } if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) return NULL; val = pr->u.value; if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) return NULL; return JS_ToCString(ctx, val); } /* in order to avoid executing arbitrary code during the stack trace generation, we only look at simple 'name' properties containing a string. */ static const char *get_func_name(JSContext *ctx, JSValueConst func) { JSProperty *pr; JSShapeProperty *prs; JSValueConst val; if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) return NULL; prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); if (!prs) return NULL; if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) return NULL; val = pr->u.value; if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) return NULL; return JS_ToCString(ctx, val); } #define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) /* if filename != NULL, an additional level is added with the filename and line number information (used for parse error). */ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, const char *filename, int line_num, int col_num, int backtrace_flags) { JSStackFrame *sf; JSValue str; DynBuf dbuf; const char *func_name_str; const char *str1; JSObject *p; if (!JS_IsObject(error_obj)) return; /* protection in the out of memory case */ js_dbuf_init(ctx, &dbuf); if (filename) { dbuf_printf(&dbuf, " at %s", filename); if (line_num != -1) dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); dbuf_putc(&dbuf, '\n'); str = JS_NewString(ctx, filename); if (JS_IsException(str)) return; /* Note: SpiderMonkey does that, could update once there is a standard */ if (JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0 || JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0 || JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_columnNumber, JS_NewInt32(ctx, col_num), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0) { return; } } for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) break; if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; continue; } func_name_str = get_func_name(ctx, sf->cur_func); if (!func_name_str || func_name_str[0] == '\0') str1 = ""; else str1 = func_name_str; dbuf_printf(&dbuf, " at %s", str1); JS_FreeCString(ctx, func_name_str); p = JS_VALUE_GET_OBJ(sf->cur_func); if (js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b; const char *atom_str; int line_num1, col_num1; b = p->u.func.function_bytecode; if (b->has_debug) { line_num1 = find_line_num(ctx, b, sf->cur_pc - b->byte_code_buf - 1, &col_num1); atom_str = JS_AtomToCString(ctx, b->debug.filename); dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : ""); JS_FreeCString(ctx, atom_str); if (line_num1 != 0) dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); dbuf_putc(&dbuf, ')'); } } else { dbuf_printf(&dbuf, " (native)"); } dbuf_putc(&dbuf, '\n'); } dbuf_putc(&dbuf, '\0'); if (dbuf_error(&dbuf)) str = JS_NULL; else str = JS_NewString(ctx, (char *)dbuf.buf); dbuf_free(&dbuf); JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, str, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } /* Note: it is important that no exception is returned by this function */ static BOOL is_backtrace_needed(JSContext *ctx, JSValueConst obj) { JSObject *p; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return FALSE; p = JS_VALUE_GET_OBJ(obj); if (p->class_id != JS_CLASS_ERROR) return FALSE; if (find_own_property1(p, JS_ATOM_stack)) return FALSE; return TRUE; } JSValue JS_NewError(JSContext *ctx) { return JS_NewObjectClass(ctx, JS_CLASS_ERROR); } static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace) { char buf[256]; JSValue obj, ret; vsnprintf(buf, sizeof(buf), fmt, ap); obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], JS_CLASS_ERROR); if (unlikely(JS_IsException(obj))) { /* out of memory: throw JS_NULL to avoid recursing */ obj = JS_NULL; } else { JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, JS_NewString(ctx, buf), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); if (add_backtrace) { build_backtrace(ctx, obj, NULL, 0, 0, 0); } } ret = JS_Throw(ctx, obj); return ret; } static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) { JSRuntime *rt = ctx->rt; JSStackFrame *sf; BOOL add_backtrace; /* the backtrace is added later if called from a bytecode function */ sf = rt->current_stack_frame; add_backtrace = !rt->in_out_of_memory && (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); return JS_ThrowError2(ctx, error_num, fmt, ap, add_backtrace); } JSValue __attribute__((format(printf, 2, 3))) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start(ap, fmt); val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap); va_end(ap); return val; } JSValue __attribute__((format(printf, 2, 3))) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start(ap, fmt); val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); va_end(ap); return val; } static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, const char *fmt, ...) { va_list ap; if (flags & JS_PROP_THROW || flags & JS_PROP_THROW_STRICT) { va_start(ap, fmt); JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); va_end(ap); return -1; } else { return FALSE; } } /* never use it directly */ static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) { char buf[ATOM_GET_STR_BUF_SIZE]; return JS_ThrowTypeError(ctx, fmt, JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); } /* never use it directly */ static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) { char buf[ATOM_GET_STR_BUF_SIZE]; return JS_ThrowSyntaxError(ctx, fmt, JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); } /* %s is replaced by 'atom'. The macro is used so that gcc can check the format string. */ #define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "") #define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "") static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) { if (flags & JS_PROP_THROW || flags & JS_PROP_THROW_STRICT) { JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); return -1; } else { return FALSE; } } JSValue __attribute__((format(printf, 2, 3))) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start(ap, fmt); val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap); va_end(ap); return val; } JSValue __attribute__((format(printf, 2, 3))) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start(ap, fmt); val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap); va_end(ap); return val; } JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start(ap, fmt); val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap); va_end(ap); return val; } JSValue JS_ThrowOutOfMemory(JSContext *ctx) { JSRuntime *rt = ctx->rt; if (!rt->in_out_of_memory) { rt->in_out_of_memory = TRUE; JS_ThrowInternalError(ctx, "out of memory"); rt->in_out_of_memory = FALSE; } return JS_EXCEPTION; } static JSValue JS_ThrowStackOverflow(JSContext *ctx) { return JS_ThrowInternalError(ctx, "stack overflow"); } static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) { return JS_ThrowTypeError(ctx, "not an object"); } static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) { char buf[ATOM_GET_STR_BUF_SIZE]; return JS_ThrowReferenceError(ctx, "'%s' is not defined", JS_AtomGetStr(ctx, buf, sizeof(buf), name)); } static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) { char buf[ATOM_GET_STR_BUF_SIZE]; return JS_ThrowReferenceError(ctx, "%s is not initialized", name == JS_ATOM_NULL ? "lexical variable" : JS_AtomGetStr(ctx, buf, sizeof(buf), name)); } static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, JSFunctionBytecode *b, int idx, BOOL is_ref) { JSAtom atom = JS_ATOM_NULL; if (is_ref) { atom = b->closure_var[idx].var_name; } else { /* not present if the function is stripped and contains no eval() */ if (b->vardefs) atom = b->vardefs[b->arg_count + idx].var_name; } return JS_ThrowReferenceErrorUninitialized(ctx, atom); } static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) { JSRuntime *rt = ctx->rt; JSAtom name; name = rt->class_array[class_id].class_name; return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); } static void JS_ThrowInterrupted(JSContext *ctx) { JS_ThrowInternalError(ctx, "interrupted"); JS_SetUncatchableException(ctx, TRUE); } static no_inline __exception int __js_poll_interrupts(JSContext *ctx) { JSRuntime *rt = ctx->rt; ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; if (rt->interrupt_handler) { if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { JS_ThrowInterrupted(ctx); return -1; } } return 0; } static inline __exception int js_poll_interrupts(JSContext *ctx) { if (unlikely(--ctx->interrupt_counter <= 0)) { return __js_poll_interrupts(ctx); } else { return 0; } } static void JS_SetImmutablePrototype(JSContext *ctx, JSValueConst obj) { JSObject *p; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return; p = JS_VALUE_GET_OBJ(obj); p->has_immutable_prototype = TRUE; } /* Return -1 (exception) or TRUE. */ static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) { JSObject *proto, *p, *p1; JSShape *sh; if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL) goto not_obj; p = JS_VALUE_GET_OBJ(obj); if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { not_obj: JS_ThrowTypeErrorNotAnObject(ctx); return -1; } proto = NULL; } else proto = JS_VALUE_GET_OBJ(proto_val); if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return TRUE; sh = p->shape; if (sh->proto == proto) return TRUE; if (unlikely(p->has_immutable_prototype)) { JS_ThrowTypeError(ctx, "prototype is immutable"); return -1; } if (unlikely(!p->extensible)) { JS_ThrowTypeError(ctx, "object is not extensible"); return -1; } if (proto) { /* check if there is a cycle */ p1 = proto; do { if (p1 == p) { JS_ThrowTypeError(ctx, "circular prototype chain"); return -1; } /* Note: for Proxy objects, proto is NULL */ p1 = p1->shape->proto; } while (p1 != NULL); JS_DupValue(ctx, proto_val); } if (js_shape_prepare_update(ctx, p, NULL)) return -1; sh = p->shape; if (sh->proto) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); sh->proto = proto; return TRUE; } /* return -1 (exception) or TRUE/FALSE */ int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) { return JS_SetPrototypeInternal(ctx, obj, proto_val); } /* Return an Object, JS_NULL or JS_EXCEPTION in case of exotic object. */ JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) { JSValue val; if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { JSObject *p; p = JS_VALUE_GET_OBJ(obj); p = p->shape->proto; if (!p) val = JS_NULL; else val = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); } else { /* Primitives have no prototype */ val = JS_NULL; } return val; } static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) { JSValue obj1; obj1 = JS_GetPrototype(ctx, obj); JS_FreeValue(ctx, obj); return obj1; } /* return the value associated to the autoinit property or an exception */ typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); static JSAutoInitFunc *js_autoinit_func_table[] = { js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ }; /* warning: 'prs' is reallocated after it */ static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSProperty *pr, JSShapeProperty *prs) { JSValue val; JSContext *realm; JSAutoInitFunc *func; JSAutoInitIDEnum id; if (js_shape_prepare_update(ctx, p, &prs)) return -1; realm = js_autoinit_get_realm(pr); id = js_autoinit_get_id(pr); func = js_autoinit_func_table[id]; /* 'func' shall not modify the object properties 'pr' */ val = func(realm, p, prop, pr->u.init.opaque); js_autoinit_free(ctx->rt, pr); prs->flags &= ~JS_PROP_TMASK; pr->u.value = JS_NULL; if (JS_IsException(val)) return -1; pr->u.value = val; return 0; } JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValueConst this_obj, BOOL throw_ref_error) { JSObject *p; JSProperty *pr; JSShapeProperty *prs; uint32_t tag; tag = JS_VALUE_GET_TAG(obj); if (unlikely(tag != JS_TAG_OBJECT)) { switch(tag) { case JS_TAG_NULL: return JS_NULL; case JS_TAG_EXCEPTION: return JS_EXCEPTION; case JS_TAG_STRING: { JSString *p1 = JS_VALUE_GET_STRING(obj); /* Only indexed access via [] is allowed on strings */ if (__JS_AtomIsTaggedInt(prop)) { uint32_t idx = __JS_AtomToUInt32(prop); if (idx < p1->len) { return js_new_string_char(ctx, string_get(p1, idx)); } } } return JS_NULL; case JS_TAG_STRING_ROPE: { JSStringRope *p1 = JS_VALUE_GET_STRING_ROPE(obj); /* Only indexed access via [] is allowed on strings */ if (__JS_AtomIsTaggedInt(prop)) { uint32_t idx = __JS_AtomToUInt32(prop); if (idx < p1->len) { return js_new_string_char(ctx, string_rope_get(obj, idx)); } } } return JS_NULL; default: /* Primitives have no properties */ return JS_NULL; } } else { p = JS_VALUE_GET_OBJ(obj); } for(;;) { prs = find_own_property(&pr, p, prop); if (prs) { /* found */ if (unlikely(prs->flags & JS_PROP_TMASK)) { if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { if (unlikely(!pr->u.getset.getter)) { return JS_NULL; } else { JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); /* Note: the field could be removed in the getter */ func = JS_DupValue(ctx, func); return JS_CallFree(ctx, func, this_obj, 0, NULL); } } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { JSValue val = *pr->u.var_ref->pvalue; if (unlikely(JS_IsUninitialized(val))) return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return JS_DupValue(ctx, val); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* Instantiate property and retry */ if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) return JS_EXCEPTION; continue; } } else { return JS_DupValue(ctx, pr->u.value); } } if (unlikely(p->is_exotic)) { /* exotic behaviors */ if (p->fast_array) { if (__JS_AtomIsTaggedInt(prop)) { uint32_t idx = __JS_AtomToUInt32(prop); if (idx < p->u.array.count) { /* we avoid duplicating the code */ return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); } } } else { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em) { if (em->get_own_property) { JSPropertyDescriptor desc; int ret; JSValue obj1; /* Note: if 'p' is a prototype, it can be freed in the called function */ obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); ret = em->get_own_property(ctx, &desc, obj1, prop); JS_FreeValue(ctx, obj1); if (ret < 0) return JS_EXCEPTION; if (ret) { if (desc.flags & JS_PROP_GETSET) { JS_FreeValue(ctx, desc.setter); return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); } else { return desc.value; } } } } } } p = p->shape->proto; if (!p) break; } if (unlikely(throw_ref_error)) { return JS_ThrowReferenceErrorNotDefined(ctx, prop); } else { return JS_NULL; } } static uint32_t js_string_obj_get_length(JSContext *ctx, JSValueConst obj) { JSObject *p; uint32_t len = 0; /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ p = JS_VALUE_GET_OBJ(obj); if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { JSString *p1 = JS_VALUE_GET_STRING(p->u.object_data); len = p1->len; } return len; } static int num_keys_cmp(const void *p1, const void *p2, void *opaque) { JSContext *ctx = opaque; JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; uint32_t v1, v2; BOOL atom1_is_integer, atom2_is_integer; atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); assert(atom1_is_integer && atom2_is_integer); if (v1 < v2) return -1; else if (v1 == v2) return 0; else return 1; } void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) { uint32_t i; if (tab) { for(i = 0; i < len; i++) JS_FreeAtom(ctx, tab[i].atom); js_free(ctx, tab); } } /* return < 0 in case if exception, 0 if OK. ptab and its atoms must be freed by the user. */ static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSObject *p, int flags) { int i, j; JSShape *sh; JSShapeProperty *prs; JSPropertyEnum *tab_atom, *tab_exotic; JSAtom atom; uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; BOOL is_enumerable, num_sorted; uint32_t num_key; JSAtomKindEnum kind; /* clear pointer for consistency in case of failure */ *ptab = NULL; *plen = 0; /* compute the number of returned properties */ num_keys_count = 0; str_keys_count = 0; sym_keys_count = 0; exotic_keys_count = 0; exotic_count = 0; tab_exotic = NULL; sh = p->shape; for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { atom = prs->atom; if (atom != JS_ATOM_NULL) { /* Skip object-key symbols (private access tokens, not enumerable) */ if (js_atom_is_object_key_symbol(ctx->rt, atom)) continue; is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); kind = JS_AtomGetKind(ctx, atom); if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && ((flags >> kind) & 1) != 0) { /* need to raise an exception in case of the module name space (implicit GetOwnProperty) */ if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { JSVarRef *var_ref = p->prop[i].u.var_ref; if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return -1; } } if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { num_keys_count++; } else if (kind == JS_ATOM_KIND_STRING) { str_keys_count++; } else { sym_keys_count++; } } } } if (p->is_exotic) { if (p->fast_array) { if (flags & JS_GPN_STRING_MASK) { num_keys_count += p->u.array.count; } } else if (p->class_id == JS_CLASS_STRING) { if (flags & JS_GPN_STRING_MASK) { num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); } } } /* fill them */ atom_count = num_keys_count + str_keys_count; if (atom_count < str_keys_count) goto add_overflow; atom_count += sym_keys_count; if (atom_count < sym_keys_count) goto add_overflow; atom_count += exotic_keys_count; if (atom_count < exotic_keys_count || atom_count > INT32_MAX) { add_overflow: JS_ThrowOutOfMemory(ctx); JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); return -1; } /* XXX: need generic way to test for js_malloc(ctx, a * b) overflow */ /* avoid allocating 0 bytes */ tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); if (!tab_atom) { JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); return -1; } num_index = 0; str_index = num_keys_count; sym_index = str_index + str_keys_count; num_sorted = TRUE; sh = p->shape; for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { atom = prs->atom; if (atom != JS_ATOM_NULL) { /* Skip object-key symbols (private access tokens, not enumerable) */ if (js_atom_is_object_key_symbol(ctx->rt, atom)) continue; is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); kind = JS_AtomGetKind(ctx, atom); if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && ((flags >> kind) & 1) != 0) { if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { j = num_index++; num_sorted = FALSE; } else if (kind == JS_ATOM_KIND_STRING) { j = str_index++; } else { j = sym_index++; } tab_atom[j].atom = JS_DupAtom(ctx, atom); tab_atom[j].is_enumerable = is_enumerable; } } } if (p->is_exotic) { int len; if (p->fast_array) { if (flags & JS_GPN_STRING_MASK) { len = p->u.array.count; goto add_array_keys; } } else if (p->class_id == JS_CLASS_STRING) { if (flags & JS_GPN_STRING_MASK) { len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); add_array_keys: for(i = 0; i < len; i++) { tab_atom[num_index].atom = __JS_AtomFromUInt32(i); if (tab_atom[num_index].atom == JS_ATOM_NULL) { JS_FreePropertyEnum(ctx, tab_atom, num_index); return -1; } tab_atom[num_index].is_enumerable = TRUE; num_index++; } } } else { /* Note: exotic keys are not reordered and comes after the object own properties. */ for(i = 0; i < exotic_count; i++) { atom = tab_exotic[i].atom; is_enumerable = tab_exotic[i].is_enumerable; kind = JS_AtomGetKind(ctx, atom); if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && ((flags >> kind) & 1) != 0) { tab_atom[sym_index].atom = atom; tab_atom[sym_index].is_enumerable = is_enumerable; sym_index++; } else { JS_FreeAtom(ctx, atom); } } js_free(ctx, tab_exotic); } } assert(num_index == num_keys_count); assert(str_index == num_keys_count + str_keys_count); assert(sym_index == atom_count); if (num_keys_count != 0 && !num_sorted) { rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, ctx); } *ptab = tab_atom; *plen = atom_count; return 0; } int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj, int flags) { if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { JS_ThrowTypeErrorNotAnObject(ctx); return -1; } return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, JS_VALUE_GET_OBJ(obj), flags); } /* Return -1 if exception, FALSE if the property does not exist, TRUE if it exists. If TRUE is returned, the property descriptor 'desc' is filled present. */ static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, JSObject *p, JSAtom prop) { JSShapeProperty *prs; JSProperty *pr; retry: prs = find_own_property(&pr, p, prop); if (prs) { if (desc) { desc->flags = prs->flags & JS_PROP_C_W_E; desc->getter = JS_NULL; desc->setter = JS_NULL; desc->value = JS_NULL; if (unlikely(prs->flags & JS_PROP_TMASK)) { if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { desc->flags |= JS_PROP_GETSET; if (pr->u.getset.getter) desc->getter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); if (pr->u.getset.setter) desc->setter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { JSValue val = *pr->u.var_ref->pvalue; if (unlikely(JS_IsUninitialized(val))) { JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return -1; } desc->value = JS_DupValue(ctx, val); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* Instantiate property and retry */ if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) return -1; goto retry; } } else { desc->value = JS_DupValue(ctx, pr->u.value); } } else { /* for consistency, send the exception even if desc is NULL */ if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return -1; } } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* nothing to do: delay instantiation until actual value and/or attributes are read */ } } return TRUE; } if (p->is_exotic) { if (p->fast_array) { /* specific case for fast arrays */ if (__JS_AtomIsTaggedInt(prop)) { uint32_t idx; idx = __JS_AtomToUInt32(prop); if (idx < p->u.array.count) { if (desc) { desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE; desc->getter = JS_NULL; desc->setter = JS_NULL; desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); } return TRUE; } } } else { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em && em->get_own_property) { return em->get_own_property(ctx, desc, JS_MKPTR(JS_TAG_OBJECT, p), prop); } } } return FALSE; } int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj, JSAtom prop) { if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { JS_ThrowTypeErrorNotAnObject(ctx); return -1; } return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); } /* return -1 if exception (exotic object only) or TRUE/FALSE */ int JS_IsExtensible(JSContext *ctx, JSValueConst obj) { JSObject *p; if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) return FALSE; p = JS_VALUE_GET_OBJ(obj); return p->extensible; } /* return -1 if exception (exotic object only) or TRUE/FALSE */ int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) { JSObject *p; if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) return FALSE; p = JS_VALUE_GET_OBJ(obj); p->extensible = FALSE; return TRUE; } /* return -1 if exception otherwise TRUE or FALSE */ int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) { JSObject *p; int ret; if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) return FALSE; p = JS_VALUE_GET_OBJ(obj); for(;;) { /* JS_GetOwnPropertyInternal can free the prototype */ JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); if (ret != 0) return ret; p = p->shape->proto; if (!p) break; } return FALSE; } /* val must be a symbol */ static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val) { JSAtomStruct *p = JS_VALUE_GET_PTR(val); return js_get_atom_index(ctx->rt, p); } /* return JS_ATOM_NULL in case of exception */ JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) { JSAtom atom; uint32_t tag; tag = JS_VALUE_GET_TAG(val); if (tag == JS_TAG_INT && (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { /* fast path for integer values */ atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); } else if (tag == JS_TAG_SYMBOL) { JSAtomStruct *p = JS_VALUE_GET_PTR(val); atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); } else { JSValue str; str = JS_ToPropertyKey(ctx, val); if (JS_IsException(str)) return JS_ATOM_NULL; if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { /* Must dup atom for proper ownership (especially for object-key symbols) */ atom = JS_DupAtom(ctx, js_symbol_to_atom(ctx, str)); } else { atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); } } return atom; } static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, JSValue prop) { JSAtom atom; JSValue ret; uint32_t prop_tag = JS_VALUE_GET_TAG(prop); if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && prop_tag == JS_TAG_INT)) { JSObject *p; uint32_t idx; int32_t signed_idx; /* fast path for array access */ p = JS_VALUE_GET_OBJ(this_obj); signed_idx = JS_VALUE_GET_INT(prop); switch(p->class_id) { case JS_CLASS_ARRAY: /* arrays require non-negative numeric index - return null for invalid */ if (signed_idx < 0) { JS_FreeValue(ctx, prop); return JS_NULL; } idx = (uint32_t)signed_idx; if (unlikely(idx >= p->u.array.count)) goto slow_path; return JS_DupValue(ctx, p->u.array.u.values[idx]); default: goto slow_path; } } else { slow_path: /* Type checking for array vs object indexing - return null for invalid retrieval */ if (JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(this_obj); if (p->class_id == JS_CLASS_ARRAY) { /* Arrays require numeric index - return null for non-numeric */ if (prop_tag != JS_TAG_INT && !JS_TAG_IS_FLOAT64(prop_tag)) { JS_FreeValue(ctx, prop); return JS_NULL; } /* Return null for negative index */ if (prop_tag == JS_TAG_INT) { if (JS_VALUE_GET_INT(prop) < 0) { JS_FreeValue(ctx, prop); return JS_NULL; } } else { double d = JS_VALUE_GET_FLOAT64(prop); if (d < 0) { JS_FreeValue(ctx, prop); return JS_NULL; } } } else { /* Objects require text or non-array object (symbol) key - return null for invalid */ if (prop_tag == JS_TAG_OBJECT) { /* Check if it's an array - arrays not allowed as object keys */ JSObject *key_obj = JS_VALUE_GET_OBJ(prop); if (key_obj->class_id == JS_CLASS_ARRAY) { JS_FreeValue(ctx, prop); return JS_NULL; } } else if (prop_tag != JS_TAG_STRING && prop_tag != JS_TAG_STRING_ROPE && prop_tag != JS_TAG_SYMBOL) { JS_FreeValue(ctx, prop); return JS_NULL; } } } /* ToObject() must be done before ToPropertyKey() */ atom = JS_ValueToAtom(ctx, prop); if (JS_IsNull(this_obj)) { JS_FreeValue(ctx, prop); JSValue ret = JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", atom); JS_FreeAtom(ctx, atom); return ret; } JS_FreeValue(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) return JS_EXCEPTION; ret = JS_GetProperty(ctx, this_obj, atom); JS_FreeAtom(ctx, atom); return ret; } } JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, uint32_t idx) { return JS_GetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx)); } /* Check if an object has a generalized numeric property. Return value: -1 for exception, TRUE if property exists, stored into *pval, FALSE if proprty does not exist. */ static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval) { JSValue val = JS_NULL; JSAtom prop; int present; if (likely((uint64_t)idx <= JS_ATOM_MAX_INT)) { /* fast path */ present = JS_HasProperty(ctx, obj, __JS_AtomFromUInt32(idx)); if (present > 0) { val = JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx)); if (unlikely(JS_IsException(val))) present = -1; } } else { prop = JS_NewAtomInt64(ctx, idx); present = -1; if (likely(prop != JS_ATOM_NULL)) { present = JS_HasProperty(ctx, obj, prop); if (present > 0) { val = JS_GetProperty(ctx, obj, prop); if (unlikely(JS_IsException(val))) present = -1; } JS_FreeAtom(ctx, prop); } } *pval = val; return present; } static JSValue JS_GetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx) { JSAtom prop; JSValue val; if ((uint64_t)idx <= INT32_MAX) { /* fast path for fast arrays */ return JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx)); } prop = JS_NewAtomInt64(ctx, idx); if (prop == JS_ATOM_NULL) return JS_EXCEPTION; val = JS_GetProperty(ctx, obj, prop); JS_FreeAtom(ctx, prop); return val; } JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj, const char *prop) { JSAtom atom; JSValue ret; atom = JS_NewAtom(ctx, prop); if (atom == JS_ATOM_NULL) return JS_EXCEPTION; ret = JS_GetProperty(ctx, this_obj, atom); JS_FreeAtom(ctx, atom); return ret; } /* Note: the property value is not initialized. Return NULL if memory error. */ static JSProperty *add_property(JSContext *ctx, JSObject *p, JSAtom prop, int prop_flags) { JSShape *sh, *new_sh; sh = p->shape; if (sh->is_hashed) { /* try to find an existing shape */ new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); if (new_sh) { /* matching shape found: use it */ /* the property array may need to be resized */ if (new_sh->prop_size != sh->prop_size) { JSProperty *new_prop; new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * new_sh->prop_size); if (!new_prop) return NULL; p->prop = new_prop; } p->shape = js_dup_shape(new_sh); js_free_shape(ctx->rt, sh); return &p->prop[new_sh->prop_count - 1]; } else if (sh->header.ref_count != 1) { /* if the shape is shared, clone it */ new_sh = js_clone_shape(ctx, sh); if (!new_sh) return NULL; /* hash the cloned shape */ new_sh->is_hashed = TRUE; js_shape_hash_link(ctx->rt, new_sh); js_free_shape(ctx->rt, p->shape); p->shape = new_sh; } } assert(p->shape->header.ref_count == 1); if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) return NULL; return &p->prop[p->shape->prop_count - 1]; } /* can be called on Array or Arguments objects. return < 0 if memory alloc error. */ static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, JSObject *p) { JSProperty *pr; JSShape *sh; JSValue *tab; uint32_t i, len, new_count; if (js_shape_prepare_update(ctx, p, NULL)) return -1; len = p->u.array.count; /* resize the properties once to simplify the error handling */ sh = p->shape; new_count = sh->prop_count + len; if (new_count > sh->prop_size) { if (resize_properties(ctx, &p->shape, p, new_count)) return -1; } tab = p->u.array.u.values; for(i = 0; i < len; i++) { /* add_property cannot fail here but __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); pr->u.value = *tab++; } js_free(ctx, p->u.array.u.values); p->u.array.count = 0; p->u.array.u.values = NULL; /* fail safe */ p->u.array.u1.size = 0; p->fast_array = 0; return 0; } static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) { JSShape *sh; JSShapeProperty *pr, *lpr, *prop; JSProperty *pr1; uint32_t lpr_idx; intptr_t h, h1; redo: sh = p->shape; h1 = atom & sh->prop_hash_mask; h = prop_hash_end(sh)[-h1 - 1]; prop = get_shape_prop(sh); lpr = NULL; lpr_idx = 0; /* prevent warning */ while (h != 0) { pr = &prop[h - 1]; if (likely(pr->atom == atom)) { /* found ! */ if (!(pr->flags & JS_PROP_CONFIGURABLE)) return FALSE; /* realloc the shape if needed */ if (lpr) lpr_idx = lpr - get_shape_prop(sh); if (js_shape_prepare_update(ctx, p, &pr)) return -1; sh = p->shape; /* remove property */ if (lpr) { lpr = get_shape_prop(sh) + lpr_idx; lpr->hash_next = pr->hash_next; } else { prop_hash_end(sh)[-h1 - 1] = pr->hash_next; } sh->deleted_prop_count++; /* free the entry */ pr1 = &p->prop[h - 1]; free_property(ctx->rt, pr1, pr->flags); JS_FreeAtom(ctx, pr->atom); /* put default values */ pr->flags = 0; pr->atom = JS_ATOM_NULL; pr1->u.value = JS_NULL; /* compact the properties if too many deleted properties */ if (sh->deleted_prop_count >= 8 && sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { compact_properties(ctx, p); } return TRUE; } lpr = pr; h = pr->hash_next; } if (p->is_exotic) { if (p->fast_array) { uint32_t idx; if (JS_AtomIsArrayIndex(ctx, &idx, atom) && idx < p->u.array.count) { if (p->class_id == JS_CLASS_ARRAY) { /* Special case deleting the last element of a fast Array */ if (idx == p->u.array.count - 1) { JS_FreeValue(ctx, p->u.array.u.values[idx]); p->u.array.count = idx; return TRUE; } if (convert_fast_array_to_array(ctx, p)) return -1; goto redo; } else { return FALSE; } } } else { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em && em->delete_property) { return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); } } } /* not found */ return TRUE; } static int call_setter(JSContext *ctx, JSObject *setter, JSValueConst this_obj, JSValue val, int flags) { JSValue ret, func; if (likely(setter)) { func = JS_MKPTR(JS_TAG_OBJECT, setter); /* Note: the field could be removed in the setter */ func = JS_DupValue(ctx, func); ret = JS_CallFree(ctx, func, this_obj, 1, (JSValueConst *)&val); JS_FreeValue(ctx, val); if (JS_IsException(ret)) return -1; JS_FreeValue(ctx, ret); return TRUE; } else { JS_FreeValue(ctx, val); if (flags & JS_PROP_THROW || flags & JS_PROP_THROW_STRICT) { JS_ThrowTypeError(ctx, "no setter for property"); return -1; } return FALSE; } } /* set the array length and remove the array elements if necessary. */ static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, int flags) { uint32_t len, idx, cur_len; int i, ret; /* Note: this call can reallocate the properties of 'p' */ ret = JS_ToArrayLengthFree(ctx, &len, val, FALSE); if (ret) return -1; /* JS_ToArrayLengthFree() must be done before the read-only test */ if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); if (likely(p->fast_array)) { uint32_t old_len = p->u.array.count; if (len < old_len) { for(i = len; i < old_len; i++) { JS_FreeValue(ctx, p->u.array.u.values[i]); } p->u.array.count = len; } p->prop[0].u.value = JS_NewUint32(ctx, len); } else { /* Note: length is always a uint32 because the object is an array */ JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); if (len < cur_len) { uint32_t d; JSShape *sh; JSShapeProperty *pr; d = cur_len - len; sh = p->shape; if (d <= sh->prop_count) { JSAtom atom; /* faster to iterate */ while (cur_len > len) { atom = JS_NewAtomUInt32(ctx, cur_len - 1); ret = delete_property(ctx, p, atom); JS_FreeAtom(ctx, atom); if (unlikely(!ret)) { /* unlikely case: property is not configurable */ break; } cur_len--; } } else { /* faster to iterate thru all the properties. Need two passes in case one of the property is not configurable */ cur_len = len; for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { if (pr->atom != JS_ATOM_NULL && JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { if (idx >= cur_len && !(pr->flags & JS_PROP_CONFIGURABLE)) { cur_len = idx + 1; } } } for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { if (pr->atom != JS_ATOM_NULL && JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { if (idx >= cur_len) { /* remove the property */ delete_property(ctx, p, pr->atom); /* WARNING: the shape may have been modified */ sh = p->shape; pr = get_shape_prop(sh) + i; } } } } } else { cur_len = len; } set_value(ctx, &p->prop[0].u.value, JS_NewUint32(ctx, cur_len)); if (unlikely(cur_len > len)) { return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); } } return TRUE; } /* return -1 if exception */ static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) { uint32_t new_size; size_t slack; JSValue *new_array_prop; /* XXX: potential arithmetic overflow */ new_size = max_int(new_len, p->u.array.u1.size * 3 / 2); new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); if (!new_array_prop) return -1; new_size += slack / sizeof(*new_array_prop); p->u.array.u.values = new_array_prop; p->u.array.u1.size = new_size; return 0; } /* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = TRUE and p->extensible = TRUE */ static int add_fast_array_element(JSContext *ctx, JSObject *p, JSValue val, int flags) { uint32_t new_len, array_len; /* extend the array by one */ /* XXX: convert to slow array if new_len > 2^31-1 elements */ new_len = p->u.array.count + 1; /* update the length if necessary. We assume that if the length is not an integer, then if it >= 2^31. */ if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { array_len = JS_VALUE_GET_INT(p->prop[0].u.value); if (new_len > array_len) { if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { JS_FreeValue(ctx, val); return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); } p->prop[0].u.value = JS_NewInt32(ctx, new_len); } } if (unlikely(new_len > p->u.array.u1.size)) { if (expand_fast_array(ctx, p, new_len)) { JS_FreeValue(ctx, val); return -1; } } p->u.array.u.values[new_len - 1] = val; p->u.array.count = new_len; return TRUE; } /* Allocate a new fast array. Its 'length' property is set to zero. It maximum size is 2^31-1 elements. For convenience, 'len' is a 64 bit integer. WARNING: the content of the array is not initialized. */ static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) { JSValue arr; JSObject *p; if (len > INT32_MAX) return JS_ThrowRangeError(ctx, "invalid array length"); arr = JS_NewArray(ctx); if (JS_IsException(arr)) return arr; if (len > 0) { p = JS_VALUE_GET_OBJ(arr); if (expand_fast_array(ctx, p, len) < 0) { JS_FreeValue(ctx, arr); return JS_EXCEPTION; } p->u.array.count = len; } return arr; } static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) { JS_FreeValue(ctx, desc->getter); JS_FreeValue(ctx, desc->setter); JS_FreeValue(ctx, desc->value); } /* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, the new property is not added and an error is raised. 'this_obj' is the receiver. If obj != this_obj, then obj must be an object (Reflect.set case). */ int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValue val, JSValueConst this_obj, int flags) { JSObject *p, *p1; JSShapeProperty *prs; JSProperty *pr; uint32_t tag; JSPropertyDescriptor desc; int ret; tag = JS_VALUE_GET_TAG(this_obj); if (unlikely(tag != JS_TAG_OBJECT)) { if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { p = NULL; p1 = JS_VALUE_GET_OBJ(obj); goto prototype_lookup; } else { /* Primitives cannot have properties set */ JS_FreeValue(ctx, val); if (tag == JS_TAG_NULL) { JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); } else { JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' on a primitive", prop); } return -1; } } else { p = JS_VALUE_GET_OBJ(this_obj); p1 = JS_VALUE_GET_OBJ(obj); if (unlikely(p != p1)) goto retry2; } /* fast path if obj == this_obj */ retry: prs = find_own_property(&pr, p1, prop); if (prs) { if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { /* fast case */ set_value(ctx, &pr->u.value, val); return TRUE; } else if (prs->flags & JS_PROP_LENGTH) { assert(p->class_id == JS_CLASS_ARRAY); assert(prop == JS_ATOM_length); return set_array_length(ctx, p, val, flags); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { set_value(ctx, pr->u.var_ref->pvalue, val); return TRUE; } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* Instantiate property and retry (potentially useless) */ if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) { JS_FreeValue(ctx, val); return -1; } goto retry; } else { goto read_only_prop; } } for(;;) { if (p1->is_exotic) { if (p1->fast_array) { if (__JS_AtomIsTaggedInt(prop)) { uint32_t idx = __JS_AtomToUInt32(prop); if (idx < p1->u.array.count) { if (unlikely(p == p1)) return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val, flags); else break; } } } else { const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; if (em) { JSValue obj1; if (em->get_own_property) { /* get_own_property can free the prototype */ obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); ret = em->get_own_property(ctx, &desc, obj1, prop); JS_FreeValue(ctx, obj1); if (ret < 0) { JS_FreeValue(ctx, val); return ret; } if (ret) { if (desc.flags & JS_PROP_GETSET) { JSObject *setter; if (JS_IsNull(desc.setter)) setter = NULL; else setter = JS_VALUE_GET_OBJ(desc.setter); ret = call_setter(ctx, setter, this_obj, val, flags); JS_FreeValue(ctx, desc.getter); JS_FreeValue(ctx, desc.setter); return ret; } else { JS_FreeValue(ctx, desc.value); if (!(desc.flags & JS_PROP_WRITABLE)) goto read_only_prop; if (likely(p == p1)) { ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_NULL, JS_NULL, JS_PROP_HAS_VALUE); JS_FreeValue(ctx, val); return ret; } else { break; } } } } } } } p1 = p1->shape->proto; prototype_lookup: if (!p1) break; retry2: prs = find_own_property(&pr, p1, prop); if (prs) { if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* Instantiate property and retry (potentially useless) */ if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) return -1; goto retry2; } else if (!(prs->flags & JS_PROP_WRITABLE)) { goto read_only_prop; } else { break; } } } if (unlikely(flags & JS_PROP_NO_ADD)) { JS_FreeValue(ctx, val); JS_ThrowReferenceErrorNotDefined(ctx, prop); return -1; } if (unlikely(!p)) { JS_FreeValue(ctx, val); return JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); } if (unlikely(!p->extensible)) { JS_FreeValue(ctx, val); return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); } if (likely(p == JS_VALUE_GET_OBJ(obj))) { if (p->is_exotic) { if (p->class_id == JS_CLASS_ARRAY && p->fast_array && __JS_AtomIsTaggedInt(prop)) { uint32_t idx = __JS_AtomToUInt32(prop); if (idx == p->u.array.count) { /* fast case */ return add_fast_array_element(ctx, p, val, flags); } else { goto generic_create_prop; } } else { goto generic_create_prop; } } else { pr = add_property(ctx, p, prop, JS_PROP_C_W_E); if (unlikely(!pr)) { JS_FreeValue(ctx, val); return -1; } pr->u.value = val; return TRUE; } } else { /* generic case: modify the property in this_obj if it already exists */ ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); if (ret < 0) { JS_FreeValue(ctx, val); return ret; } if (ret) { if (desc.flags & JS_PROP_GETSET) { JS_FreeValue(ctx, desc.getter); JS_FreeValue(ctx, desc.setter); JS_FreeValue(ctx, val); return JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); } else { JS_FreeValue(ctx, desc.value); if (!(desc.flags & JS_PROP_WRITABLE)) { read_only_prop: JS_FreeValue(ctx, val); return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); } } ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_NULL, JS_NULL, JS_PROP_HAS_VALUE); JS_FreeValue(ctx, val); return ret; } else { generic_create_prop: ret = JS_CreateProperty(ctx, p, prop, val, JS_NULL, JS_NULL, flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_ENUMERABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_C_W_E); JS_FreeValue(ctx, val); return ret; } } } /* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, JSValue prop, JSValue val, int flags) { uint32_t prop_tag = JS_VALUE_GET_TAG(prop); if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && prop_tag == JS_TAG_INT)) { JSObject *p; uint32_t idx; int32_t signed_idx; /* fast path for array access */ p = JS_VALUE_GET_OBJ(this_obj); signed_idx = JS_VALUE_GET_INT(prop); switch(p->class_id) { case JS_CLASS_ARRAY: /* arrays require non-negative numeric index */ if (signed_idx < 0) { JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "array index must be non-negative"); return -1; } idx = (uint32_t)signed_idx; if (unlikely(idx >= (uint32_t)p->u.array.count)) { JSObject *p1; JSShape *sh1; /* fast path to add an element to the array */ if (idx != (uint32_t)p->u.array.count || !p->fast_array || !p->extensible) goto slow_path; /* check if prototype chain has a numeric property */ p1 = p->shape->proto; while (p1 != NULL) { sh1 = p1->shape; if (p1->class_id == JS_CLASS_ARRAY) { if (unlikely(!p1->fast_array)) goto slow_path; } else if (p1->class_id == JS_CLASS_OBJECT) { if (unlikely(sh1->has_small_array_index)) goto slow_path; } else { goto slow_path; } p1 = sh1->proto; } /* add element */ return add_fast_array_element(ctx, p, val, flags); } set_value(ctx, &p->u.array.u.values[idx], val); break; default: goto slow_path; } return TRUE; } else { JSAtom atom; int ret; slow_path: /* Type checking for array vs object indexing */ if (JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(this_obj); if (p->class_id == JS_CLASS_ARRAY) { /* Arrays require numeric index */ if (prop_tag != JS_TAG_INT && !JS_TAG_IS_FLOAT64(prop_tag)) { JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "array index must be a number"); return -1; } /* Check for negative index */ if (prop_tag == JS_TAG_INT) { if (JS_VALUE_GET_INT(prop) < 0) { JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "array index must be non-negative"); return -1; } } else { double d = JS_VALUE_GET_FLOAT64(prop); if (d < 0) { JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "array index must be non-negative"); return -1; } } } else { /* Objects require text or non-array object (symbol) key - NOT numbers */ if (prop_tag == JS_TAG_OBJECT) { /* Check if it's an array - arrays not allowed as object keys */ JSObject *key_obj = JS_VALUE_GET_OBJ(prop); if (key_obj->class_id == JS_CLASS_ARRAY) { JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "object key must be text or object, not array"); return -1; } } else if (prop_tag != JS_TAG_STRING && prop_tag != JS_TAG_STRING_ROPE && prop_tag != JS_TAG_SYMBOL) { JS_FreeValue(ctx, prop); JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "object key must be text or object"); return -1; } } } atom = JS_ValueToAtom(ctx, prop); JS_FreeValue(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) { JS_FreeValue(ctx, val); return -1; } ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, flags); JS_FreeAtom(ctx, atom); return ret; } } int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, uint32_t idx, JSValue val) { return JS_SetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx), val, JS_PROP_THROW); } int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj, int64_t idx, JSValue val) { JSAtom prop; int res; if ((uint64_t)idx <= INT32_MAX) { /* fast path for fast arrays */ return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val, JS_PROP_THROW); } prop = JS_NewAtomInt64(ctx, idx); if (prop == JS_ATOM_NULL) { JS_FreeValue(ctx, val); return -1; } res = JS_SetProperty(ctx, this_obj, prop, val); JS_FreeAtom(ctx, prop); return res; } int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, const char *prop, JSValue val) { JSAtom atom; int ret; atom = JS_NewAtom(ctx, prop); if (atom == JS_ATOM_NULL) { JS_FreeValue(ctx, val); return -1; } ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, JS_PROP_THROW); JS_FreeAtom(ctx, atom); return ret; } /* compute the property flags. For each flag: (JS_PROP_HAS_x forces it, otherwise def_flags is used) Note: makes assumption about the bit pattern of the flags */ static int get_prop_flags(int flags, int def_flags) { int mask; mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; return (flags & mask) | (def_flags & ~mask); } static int JS_CreateProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { JSProperty *pr; int ret, prop_flags; /* add a new property or modify an existing exotic one */ if (p->is_exotic) { if (p->class_id == JS_CLASS_ARRAY) { uint32_t idx, len; if (p->fast_array) { if (__JS_AtomIsTaggedInt(prop)) { idx = __JS_AtomToUInt32(prop); if (idx == p->u.array.count) { if (!p->extensible) goto not_extensible; if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) goto convert_to_array; prop_flags = get_prop_flags(flags, 0); if (prop_flags != JS_PROP_C_W_E) goto convert_to_array; return add_fast_array_element(ctx, p, JS_DupValue(ctx, val), flags); } else { goto convert_to_array; } } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { /* convert the fast array to normal array */ convert_to_array: if (convert_fast_array_to_array(ctx, p)) return -1; goto generic_array; } } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { JSProperty *plen; JSShapeProperty *pslen; generic_array: /* update the length field */ plen = &p->prop[0]; JS_ToUint32(ctx, &len, plen->u.value); if ((idx + 1) > len) { pslen = get_shape_prop(p->shape); if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); /* XXX: should update the length after defining the property */ len = idx + 1; set_value(ctx, &plen->u.value, JS_NewUint32(ctx, len)); } } } else if (!(flags & JS_PROP_NO_EXOTIC)) { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em) { if (em->define_own_property) { return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop, val, getter, setter, flags); } ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); if (ret < 0) return -1; if (!ret) goto not_extensible; } } } if (!p->extensible) { not_extensible: return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); } if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | JS_PROP_GETSET; } else { prop_flags = flags & JS_PROP_C_W_E; } pr = add_property(ctx, p, prop, prop_flags); if (unlikely(!pr)) return -1; if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { pr->u.getset.getter = NULL; if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { pr->u.getset.getter = JS_VALUE_GET_OBJ(JS_DupValue(ctx, getter)); } pr->u.getset.setter = NULL; if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { pr->u.getset.setter = JS_VALUE_GET_OBJ(JS_DupValue(ctx, setter)); } } else { if (flags & JS_PROP_HAS_VALUE) { pr->u.value = JS_DupValue(ctx, val); } else { pr->u.value = JS_NULL; } } return TRUE; } /* return FALSE if not OK */ static BOOL check_define_prop_flags(int prop_flags, int flags) { BOOL has_accessor, is_getset; if (!(prop_flags & JS_PROP_CONFIGURABLE)) { if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { return FALSE; } if ((flags & JS_PROP_HAS_ENUMERABLE) && (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) return FALSE; if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); if (has_accessor != is_getset) return FALSE; if (!is_getset && !(prop_flags & JS_PROP_WRITABLE)) { /* not writable: cannot set the writable bit */ if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) return FALSE; } } } return TRUE; } /* ensure that the shape can be safely modified */ static int js_shape_prepare_update(JSContext *ctx, JSObject *p, JSShapeProperty **pprs) { JSShape *sh; uint32_t idx = 0; /* prevent warning */ sh = p->shape; if (sh->is_hashed) { if (sh->header.ref_count != 1) { if (pprs) idx = *pprs - get_shape_prop(sh); /* clone the shape (the resulting one is no longer hashed) */ sh = js_clone_shape(ctx, sh); if (!sh) return -1; js_free_shape(ctx->rt, p->shape); p->shape = sh; if (pprs) *pprs = get_shape_prop(sh) + idx; } else { js_shape_hash_unlink(ctx->rt, sh); sh->is_hashed = FALSE; } } return 0; } static int js_update_property_flags(JSContext *ctx, JSObject *p, JSShapeProperty **pprs, int flags) { if (flags != (*pprs)->flags) { if (js_shape_prepare_update(ctx, p, pprs)) return -1; (*pprs)->flags = flags; } return 0; } /* allowed flags: JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, JS_PROP_THROW, JS_PROP_NO_EXOTIC. If JS_PROP_THROW is set, return an exception instead of FALSE. if JS_PROP_NO_EXOTIC is set, do not call the exotic define_own_property callback. return -1 (exception), FALSE or TRUE. */ int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; int mask, res; if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { JS_ThrowTypeErrorNotAnObject(ctx); return -1; } p = JS_VALUE_GET_OBJ(this_obj); redo_prop_update: prs = find_own_property(&pr, p, prop); if (prs) { /* the range of the Array length property is always tested before */ if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { uint32_t array_length; if (JS_ToArrayLengthFree(ctx, &array_length, JS_DupValue(ctx, val), FALSE)) { return -1; } /* this code relies on the fact that Uint32 are never allocated */ val = (JSValueConst)JS_NewUint32(ctx, array_length); /* prs may have been modified */ prs = find_own_property(&pr, p, prop); assert(prs != NULL); } /* property already exists */ if (!check_define_prop_flags(prs->flags, flags)) { not_configurable: return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); } if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* Instantiate property and retry */ if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) return -1; goto redo_prop_update; } if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { JSObject *new_getter, *new_setter; if (JS_IsFunction(ctx, getter)) { new_getter = JS_VALUE_GET_OBJ(getter); } else { new_getter = NULL; } if (JS_IsFunction(ctx, setter)) { new_setter = JS_VALUE_GET_OBJ(setter); } else { new_setter = NULL; } if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { if (js_shape_prepare_update(ctx, p, &prs)) return -1; /* convert to getset */ if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { free_var_ref(ctx->rt, pr->u.var_ref); } else { JS_FreeValue(ctx, pr->u.value); } prs->flags = (prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | JS_PROP_GETSET; pr->u.getset.getter = NULL; pr->u.getset.setter = NULL; } else { if (!(prs->flags & JS_PROP_CONFIGURABLE)) { if ((flags & JS_PROP_HAS_GET) && new_getter != pr->u.getset.getter) { goto not_configurable; } if ((flags & JS_PROP_HAS_SET) && new_setter != pr->u.getset.setter) { goto not_configurable; } } } if (flags & JS_PROP_HAS_GET) { if (pr->u.getset.getter) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); if (new_getter) JS_DupValue(ctx, getter); pr->u.getset.getter = new_getter; } if (flags & JS_PROP_HAS_SET) { if (pr->u.getset.setter) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); if (new_setter) JS_DupValue(ctx, setter); pr->u.getset.setter = new_setter; } } else { if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { /* convert to data descriptor */ if (js_shape_prepare_update(ctx, p, &prs)) return -1; if (pr->u.getset.getter) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); if (pr->u.getset.setter) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); pr->u.value = JS_NULL; } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { /* Note: JS_PROP_VARREF is always writable */ } else { if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && (flags & JS_PROP_HAS_VALUE)) { if (!js_same_value(ctx, val, pr->u.value)) { goto not_configurable; } else { return TRUE; } } } if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { if (flags & JS_PROP_HAS_VALUE) { /* update the reference */ set_value(ctx, pr->u.var_ref->pvalue, JS_DupValue(ctx, val)); } /* if writable is set to false, no longer a reference (for mapped arguments) */ if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { JSValue val1; if (js_shape_prepare_update(ctx, p, &prs)) return -1; val1 = JS_DupValue(ctx, *pr->u.var_ref->pvalue); free_var_ref(ctx->rt, pr->u.var_ref); pr->u.value = val1; prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); } } else if (prs->flags & JS_PROP_LENGTH) { if (flags & JS_PROP_HAS_VALUE) { /* Note: no JS code is executable because 'val' is guaranted to be a Uint32 */ res = set_array_length(ctx, p, JS_DupValue(ctx, val), flags); } else { res = TRUE; } /* still need to reset the writable flag if needed. The JS_PROP_LENGTH is kept because the Uint32 test is still done if the length property is read-only. */ if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { prs = get_shape_prop(p->shape); if (js_update_property_flags(ctx, p, &prs, prs->flags & ~JS_PROP_WRITABLE)) return -1; } return res; } else { if (flags & JS_PROP_HAS_VALUE) { JS_FreeValue(ctx, pr->u.value); pr->u.value = JS_DupValue(ctx, val); } if (flags & JS_PROP_HAS_WRITABLE) { if (js_update_property_flags(ctx, p, &prs, (prs->flags & ~JS_PROP_WRITABLE) | (flags & JS_PROP_WRITABLE))) return -1; } } } } mask = 0; if (flags & JS_PROP_HAS_CONFIGURABLE) mask |= JS_PROP_CONFIGURABLE; if (flags & JS_PROP_HAS_ENUMERABLE) mask |= JS_PROP_ENUMERABLE; if (js_update_property_flags(ctx, p, &prs, (prs->flags & ~mask) | (flags & mask))) return -1; return TRUE; } /* handle modification of fast array elements */ if (p->fast_array) { uint32_t idx; uint32_t prop_flags; if (p->class_id == JS_CLASS_ARRAY) { if (__JS_AtomIsTaggedInt(prop)) { idx = __JS_AtomToUInt32(prop); if (idx < p->u.array.count) { prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); if (prop_flags != JS_PROP_C_W_E) goto convert_to_slow_array; if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { convert_to_slow_array: if (convert_fast_array_to_array(ctx, p)) return -1; else goto redo_prop_update; } if (flags & JS_PROP_HAS_VALUE) { set_value(ctx, &p->u.array.u.values[idx], JS_DupValue(ctx, val)); } return TRUE; } } } } return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); } static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSAutoInitIDEnum id, void *opaque, int flags) { JSObject *p; JSProperty *pr; if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) return FALSE; p = JS_VALUE_GET_OBJ(this_obj); if (find_own_property(&pr, p, prop)) { /* property already exists */ abort(); return FALSE; } /* Specialized CreateProperty */ pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); if (unlikely(!pr)) return -1; pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); assert((pr->u.init.realm_and_id & 3) == 0); assert(id <= 3); pr->u.init.realm_and_id |= id; pr->u.init.opaque = opaque; return TRUE; } /* shortcut to add or redefine a new property value */ int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValue val, int flags) { int ret; ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_NULL, JS_NULL, flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); JS_FreeValue(ctx, val); return ret; } int JS_DefinePropertyValueValue(JSContext *ctx, JSValueConst this_obj, JSValue prop, JSValue val, int flags) { JSAtom atom; int ret; atom = JS_ValueToAtom(ctx, prop); JS_FreeValue(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) { JS_FreeValue(ctx, val); return -1; } ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); JS_FreeAtom(ctx, atom); return ret; } int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj, uint32_t idx, JSValue val, int flags) { return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewUint32(ctx, idx), val, flags); } int JS_DefinePropertyValueInt64(JSContext *ctx, JSValueConst this_obj, int64_t idx, JSValue val, int flags) { return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx), val, flags); } int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj, const char *prop, JSValue val, int flags) { JSAtom atom; int ret; atom = JS_NewAtom(ctx, prop); if (atom == JS_ATOM_NULL) { JS_FreeValue(ctx, val); return -1; } ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); JS_FreeAtom(ctx, atom); return ret; } /* shortcut to add getter & setter */ int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValue getter, JSValue setter, int flags) { int ret; ret = JS_DefineProperty(ctx, this_obj, prop, JS_NULL, getter, setter, flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); JS_FreeValue(ctx, getter); JS_FreeValue(ctx, setter); return ret; } static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValueConst this_obj, int64_t idx, JSValue val, int flags) { return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx), val, flags | JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); } /* return TRUE if 'obj' has a non empty 'name' string */ static BOOL js_object_has_name(JSContext *ctx, JSValueConst obj) { JSProperty *pr; JSShapeProperty *prs; JSValueConst val; JSString *p; prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); if (!prs) return FALSE; if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) return TRUE; val = pr->u.value; if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) return TRUE; p = JS_VALUE_GET_STRING(val); return (p->len != 0); } static int JS_DefineObjectName(JSContext *ctx, JSValueConst obj, JSAtom name, int flags) { if (name != JS_ATOM_NULL && JS_IsObject(obj) && !js_object_has_name(ctx, obj) && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { return -1; } return 0; } static int JS_DefineObjectNameComputed(JSContext *ctx, JSValueConst obj, JSValueConst str, int flags) { if (JS_IsObject(obj) && !js_object_has_name(ctx, obj)) { JSAtom prop; JSValue name_str; prop = JS_ValueToAtom(ctx, str); if (prop == JS_ATOM_NULL) return -1; name_str = js_get_function_name(ctx, prop); JS_FreeAtom(ctx, prop); if (JS_IsException(name_str)) return -1; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) return -1; } return 0; } #define DEFINE_GLOBAL_LEX_VAR (1 << 7) #define DEFINE_GLOBAL_FUNC_VAR (1 << 6) static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) { return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); } /* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ /* XXX: could support exotic global object. */ static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) { JSObject *p; JSShapeProperty *prs; p = JS_VALUE_GET_OBJ(ctx->global_obj); prs = find_own_property1(p, prop); /* XXX: should handle JS_PROP_AUTOINIT */ if (flags & DEFINE_GLOBAL_LEX_VAR) { if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) goto fail_redeclaration; } else { if (!prs && !p->extensible) goto define_error; if (flags & DEFINE_GLOBAL_FUNC_VAR) { if (prs) { if (!(prs->flags & JS_PROP_CONFIGURABLE) && ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { define_error: JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", prop); return -1; } } } } /* check if there already is a lexical declaration */ p = JS_VALUE_GET_OBJ(ctx->global_var_obj); prs = find_own_property1(p, prop); if (prs) { fail_redeclaration: JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); return -1; } return 0; } /* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */ /* XXX: could support exotic global object. */ static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; JSValue val; int flags; if (def_flags & DEFINE_GLOBAL_LEX_VAR) { p = JS_VALUE_GET_OBJ(ctx->global_var_obj); flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) | JS_PROP_CONFIGURABLE; val = JS_UNINITIALIZED; } else { p = JS_VALUE_GET_OBJ(ctx->global_obj); flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | (def_flags & JS_PROP_CONFIGURABLE); val = JS_NULL; } prs = find_own_property1(p, prop); if (prs) return 0; if (!p->extensible) return 0; pr = add_property(ctx, p, prop, flags); if (unlikely(!pr)) return -1; pr->u.value = val; return 0; } /* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */ /* XXX: could support exotic global object. */ static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, JSValueConst func, int def_flags) { JSObject *p; JSShapeProperty *prs; int flags; p = JS_VALUE_GET_OBJ(ctx->global_obj); prs = find_own_property1(p, prop); flags = JS_PROP_HAS_VALUE | JS_PROP_THROW; if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) { flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE; } if (JS_DefineProperty(ctx, ctx->global_obj, prop, func, JS_NULL, JS_NULL, flags) < 0) return -1; return 0; } static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, BOOL throw_ref_error) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; /* no exotic behavior is possible in global_var_obj */ p = JS_VALUE_GET_OBJ(ctx->global_var_obj); prs = find_own_property(&pr, p, prop); if (prs) { /* XXX: should handle JS_PROP_TMASK properties */ if (unlikely(JS_IsUninitialized(pr->u.value))) return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return JS_DupValue(ctx, pr->u.value); } return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, ctx->global_obj, throw_ref_error); } /* construct a reference to a global variable */ static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; /* no exotic behavior is possible in global_var_obj */ p = JS_VALUE_GET_OBJ(ctx->global_var_obj); prs = find_own_property(&pr, p, prop); if (prs) { /* XXX: should handle JS_PROP_AUTOINIT properties? */ /* XXX: conformance: do these tests in OP_put_var_ref/OP_get_var_ref ? */ if (unlikely(JS_IsUninitialized(pr->u.value))) { JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return -1; } if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); } sp[0] = JS_DupValue(ctx, ctx->global_var_obj); } else { int ret; ret = JS_HasProperty(ctx, ctx->global_obj, prop); if (ret < 0) return -1; if (ret) { sp[0] = JS_DupValue(ctx, ctx->global_obj); } else { sp[0] = JS_NULL; } } sp[1] = JS_AtomToValue(ctx, prop); return 0; } /* use for strict variable access: test if the variable exists */ static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop) { JSObject *p; JSShapeProperty *prs; int ret; /* no exotic behavior is possible in global_var_obj */ p = JS_VALUE_GET_OBJ(ctx->global_var_obj); prs = find_own_property1(p, prop); if (prs) { ret = TRUE; } else { ret = JS_HasProperty(ctx, ctx->global_obj, prop); if (ret < 0) return -1; } return ret; } /* flag = 0: normal variable write flag = 1: initialize lexical variable flag = 2: normal variable write, strict check was done before */ static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, int flag) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; int flags; /* no exotic behavior is possible in global_var_obj */ p = JS_VALUE_GET_OBJ(ctx->global_var_obj); prs = find_own_property(&pr, p, prop); if (prs) { /* XXX: should handle JS_PROP_AUTOINIT properties? */ if (flag != 1) { if (unlikely(JS_IsUninitialized(pr->u.value))) { JS_FreeValue(ctx, val); JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); return -1; } if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { JS_FreeValue(ctx, val); return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); } } set_value(ctx, &pr->u.value, val); return 0; } /* XXX: add a fast path where the property exists and the object is not exotic. Otherwise do as in OP_put_ref_value and remove JS_PROP_NO_ADD which is no longer necessary */ flags = JS_PROP_THROW_STRICT | JS_PROP_NO_ADD; return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, ctx->global_obj, flags); } /* return -1, FALSE or TRUE */ static int JS_DeleteGlobalVar(JSContext *ctx, JSAtom prop) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; int ret; /* 9.1.1.4.7 DeleteBinding ( N ) */ p = JS_VALUE_GET_OBJ(ctx->global_var_obj); prs = find_own_property(&pr, p, prop); if (prs) return FALSE; /* lexical variables cannot be deleted */ ret = JS_HasProperty(ctx, ctx->global_obj, prop); if (ret < 0) return -1; if (ret) { return JS_DeleteProperty(ctx, ctx->global_obj, prop, 0); } else { return TRUE; } } /* return -1, FALSE or TRUE. return FALSE if not configurable or invalid object. return -1 in case of exception. flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags) { JSValue obj1; JSObject *p; int res; obj1 = JS_ToObject(ctx, obj); if (JS_IsException(obj1)) return -1; p = JS_VALUE_GET_OBJ(obj1); res = delete_property(ctx, p, prop); JS_FreeValue(ctx, obj1); if (res != FALSE) return res; if (flags & JS_PROP_THROW || flags & JS_PROP_THROW_STRICT) { JS_ThrowTypeError(ctx, "could not delete property"); return -1; } return FALSE; } int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags) { JSAtom prop; int res; if ((uint64_t)idx <= JS_ATOM_MAX_INT) { /* fast path for fast arrays */ return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); } prop = JS_NewAtomInt64(ctx, idx); if (prop == JS_ATOM_NULL) return -1; res = JS_DeleteProperty(ctx, obj, prop, flags); JS_FreeAtom(ctx, prop); return res; } BOOL JS_IsFunction(JSContext *ctx, JSValueConst val) { JSObject *p; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return FALSE; p = JS_VALUE_GET_OBJ(val); switch(p->class_id) { case JS_CLASS_BYTECODE_FUNCTION: return TRUE; default: return (ctx->rt->class_array[p->class_id].call != NULL); } } BOOL JS_IsCFunction(JSContext *ctx, JSValueConst val, JSCFunction *func, int magic) { JSObject *p; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return FALSE; p = JS_VALUE_GET_OBJ(val); if (p->class_id == JS_CLASS_C_FUNCTION) return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); else return FALSE; } /* Constructors are no longer a separate concept - treat as functions */ BOOL JS_IsConstructor(JSContext *ctx, JSValueConst val) { return JS_IsFunction(ctx, val); } /* No-op: constructor bit is no longer used */ BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, BOOL val) { return TRUE; } BOOL JS_IsError(JSContext *ctx, JSValueConst val) { JSObject *p; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return FALSE; p = JS_VALUE_GET_OBJ(val); return (p->class_id == JS_CLASS_ERROR); } /* must be called after JS_Throw() */ void JS_SetUncatchableException(JSContext *ctx, BOOL flag) { ctx->rt->current_exception_is_uncatchable = flag; } void JS_SetOpaque(JSValue obj, void *opaque) { JSObject *p; if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ(obj); p->u.opaque = opaque; } } /* return NULL if not an object of class class_id */ void *JS_GetOpaque(JSValueConst obj, JSClassID class_id) { JSObject *p; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return NULL; p = JS_VALUE_GET_OBJ(obj); if (p->class_id != class_id) return NULL; return p->u.opaque; } void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id) { void *p = JS_GetOpaque(obj, class_id); if (unlikely(!p)) { JS_ThrowTypeErrorInvalidClass(ctx, class_id); } return p; } void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id) { JSObject *p; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { *class_id = 0; return NULL; } p = JS_VALUE_GET_OBJ(obj); *class_id = p->class_id; return p->u.opaque; } static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) { JSValue ret; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return val; hint &= ~HINT_FORCE_ORDINARY; /* For string/default hint, use text() to convert */ if (hint == HINT_STRING || hint == HINT_NONE) { ret = js_cell_text(ctx, JS_NULL, 1, (JSValueConst *)&val); JS_FreeValue(ctx, val); return ret; } /* For number hint, objects cannot be converted */ JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert record to number"); } static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint) { return JS_ToPrimitiveFree(ctx, JS_DupValue(ctx, val), hint); } static int JS_ToBoolFree(JSContext *ctx, JSValue val) { uint32_t tag = JS_VALUE_GET_TAG(val); switch(tag) { case JS_TAG_INT: return JS_VALUE_GET_INT(val) != 0; case JS_TAG_BOOL: case JS_TAG_NULL: return JS_VALUE_GET_INT(val); case JS_TAG_EXCEPTION: return -1; case JS_TAG_STRING: { BOOL ret = JS_VALUE_GET_STRING(val)->len != 0; JS_FreeValue(ctx, val); return ret; } case JS_TAG_STRING_ROPE: { BOOL ret = JS_VALUE_GET_STRING_ROPE(val)->len != 0; JS_FreeValue(ctx, val); return ret; } case JS_TAG_OBJECT: JS_FreeValue(ctx, val); return 1; default: if (JS_TAG_IS_FLOAT64(tag)) { double d = JS_VALUE_GET_FLOAT64(val); return !isnan(d) && d != 0; } else { JS_FreeValue(ctx, val); return TRUE; } } } int JS_ToBool(JSContext *ctx, JSValueConst val) { return JS_ToBoolFree(ctx, JS_DupValue(ctx, val)); } static int skip_spaces(const char *pc) { const uint8_t *p, *p_next, *p_start; uint32_t c; p = p_start = (const uint8_t *)pc; for (;;) { c = *p; if (c < 128) { if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) break; p++; } else { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); if (!lre_is_space(c)) break; p = p_next; } } return p - p_start; } static inline int to_digit(int c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'A' && c <= 'Z') return c - 'A' + 10; else if (c >= 'a' && c <= 'z') return c - 'a' + 10; else return 36; } #define ATOD_INT_ONLY (1 << 0) /* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ #define ATOD_ACCEPT_BIN_OCT (1 << 2) /* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ #define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) /* accept _ between digits as a digit separator */ #define ATOD_ACCEPT_UNDERSCORES (1 << 5) /* allow a suffix to override the type */ #define ATOD_ACCEPT_SUFFIX (1 << 6) /* default type */ #define ATOD_TYPE_MASK (3 << 7) #define ATOD_TYPE_FLOAT64 (0 << 7) #define ATOD_TYPE_BIG_INT (1 << 7) /* accept -0x1 */ #define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) /* return an exception in case of memory error. Return JS_NAN if invalid syntax */ /* XXX: directly use js_atod() */ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, int radix, int flags) { const char *p, *p_start; int sep, is_neg; BOOL is_float; int atod_type = flags & ATOD_TYPE_MASK; char buf1[64], *buf; int i, j, len; BOOL buf_allocated = FALSE; JSValue val; JSATODTempMem atod_mem; /* optional separator between digits */ sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; p = str; p_start = p; is_neg = 0; if (p[0] == '+') { p++; p_start++; if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; } else if (p[0] == '-') { p++; p_start++; is_neg = 1; if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; } if (p[0] == '0') { if ((p[1] == 'x' || p[1] == 'X') && (radix == 0 || radix == 16)) { p += 2; radix = 16; } else if ((p[1] == 'o' || p[1] == 'O') && radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { p += 2; radix = 8; } else if ((p[1] == 'b' || p[1] == 'B') && radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { p += 2; radix = 2; } else if ((p[1] >= '0' && p[1] <= '9') && radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { int i; sep = 256; for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) continue; if (p[i] == '8' || p[i] == '9') goto no_prefix; p += 1; radix = 8; } else { goto no_prefix; } /* there must be a digit after the prefix */ if (to_digit((uint8_t)*p) >= radix) goto fail; no_prefix: ; } else { no_radix_prefix: if (!(flags & ATOD_INT_ONLY) && (atod_type == ATOD_TYPE_FLOAT64) && strstart(p, "Infinity", &p)) { double d = 1.0 / 0.0; if (is_neg) d = -d; val = JS_NewFloat64(ctx, d); goto done; } } if (radix == 0) radix = 10; is_float = FALSE; p_start = p; while (to_digit((uint8_t)*p) < radix || (*p == sep && (radix != 10 || p != p_start + 1 || p[-1] != '0') && to_digit((uint8_t)p[1]) < radix)) { p++; } if (!(flags & ATOD_INT_ONLY)) { if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { is_float = TRUE; p++; if (*p == sep) goto fail; while (to_digit((uint8_t)*p) < radix || (*p == sep && to_digit((uint8_t)p[1]) < radix)) p++; } if (p > p_start && (((*p == 'e' || *p == 'E') && radix == 10) || ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { const char *p1 = p + 1; is_float = TRUE; if (*p1 == '+') { p1++; } else if (*p1 == '-') { p1++; } if (is_digit((uint8_t)*p1)) { p = p1 + 1; while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1]))) p++; } } } if (p == p_start) goto fail; buf = buf1; buf_allocated = FALSE; len = p - p_start; if (unlikely((len + 2) > sizeof(buf1))) { buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ if (!buf) goto mem_error; buf_allocated = TRUE; } /* remove the separators and the radix prefixes */ j = 0; if (is_neg) buf[j++] = '-'; for (i = 0; i < len; i++) { if (p_start[i] != '_') buf[j++] = p_start[i]; } buf[j] = '\0'; if (flags & ATOD_ACCEPT_SUFFIX) { if (*p == 'n') { p++; atod_type = ATOD_TYPE_BIG_INT; } else { if (is_float && radix != 10) goto fail; } } else { if (atod_type == ATOD_TYPE_FLOAT64) { if (is_float && radix != 10) goto fail; } } switch(atod_type) { case ATOD_TYPE_FLOAT64: { double d; d = js_atod(buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, &atod_mem); /* return int or float64 */ val = JS_NewFloat64(ctx, d); } break; default: abort(); } done: if (buf_allocated) js_free_rt(ctx->rt, buf); if (pp) *pp = p; return val; fail: val = JS_NAN; goto done; mem_error: val = JS_ThrowOutOfMemory(ctx); goto done; } typedef enum JSToNumberHintEnum { TON_FLAG_NUMBER, TON_FLAG_NUMERIC, } JSToNumberHintEnum; static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, JSToNumberHintEnum flag) { uint32_t tag; JSValue ret; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_FLOAT64: case JS_TAG_INT: case JS_TAG_EXCEPTION: ret = val; break; case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val)); break; case JS_TAG_OBJECT: val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); if (JS_IsException(val)) return JS_EXCEPTION; goto redo; case JS_TAG_STRING: case JS_TAG_STRING_ROPE: { const char *str; const char *p; size_t len; str = JS_ToCStringLen(ctx, &len, val); JS_FreeValue(ctx, val); if (!str) return JS_EXCEPTION; p = str; p += skip_spaces(p); if ((p - str) == len) { ret = JS_NewInt32(ctx, 0); } else { int flags = ATOD_ACCEPT_BIN_OCT; ret = js_atof(ctx, p, &p, 0, flags); if (!JS_IsException(ret)) { p += skip_spaces(p); if ((p - str) != len) { JS_FreeValue(ctx, ret); ret = JS_NAN; } } } JS_FreeCString(ctx, str); } break; case JS_TAG_SYMBOL: JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); default: JS_FreeValue(ctx, val); ret = JS_NAN; break; } return ret; } static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) { return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); } static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) { return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); } static JSValue JS_ToNumeric(JSContext *ctx, JSValueConst val) { return JS_ToNumericFree(ctx, JS_DupValue(ctx, val)); } static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) { double d; uint32_t tag; val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) goto fail; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: d = JS_VALUE_GET_INT(val); break; case JS_TAG_FLOAT64: d = JS_VALUE_GET_FLOAT64(val); break; default: abort(); } *pres = d; return 0; fail: *pres = JS_FLOAT64_NAN; return -1; } static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) { uint32_t tag; tag = JS_VALUE_GET_TAG(val); if (tag <= JS_TAG_NULL) { *pres = JS_VALUE_GET_INT(val); return 0; } else if (JS_TAG_IS_FLOAT64(tag)) { *pres = JS_VALUE_GET_FLOAT64(val); return 0; } else { return __JS_ToFloat64Free(ctx, pres, val); } } int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val) { return JS_ToFloat64Free(ctx, pres, JS_DupValue(ctx, val)); } static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val) { return JS_ToNumberFree(ctx, JS_DupValue(ctx, val)); } /* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) { uint32_t tag; JSValue ret; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val)); break; case JS_TAG_FLOAT64: { double d = JS_VALUE_GET_FLOAT64(val); if (isnan(d)) { ret = JS_NewInt32(ctx, 0); } else { /* convert -0 to +0 */ d = trunc(d) + 0.0; ret = JS_NewFloat64(ctx, d); } } break; default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) return val; goto redo; } return ret; } /* Note: the integer value is satured to 32 bits */ static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) { uint32_t tag; int ret; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_VALUE_GET_INT(val); break; case JS_TAG_EXCEPTION: *pres = 0; return -1; case JS_TAG_FLOAT64: { double d = JS_VALUE_GET_FLOAT64(val); if (isnan(d)) { ret = 0; } else { if (d < INT32_MIN) ret = INT32_MIN; else if (d > INT32_MAX) ret = INT32_MAX; else ret = (int)d; } } break; default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { *pres = 0; return -1; } goto redo; } *pres = ret; return 0; } int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValueConst val) { return JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val)); } int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val, int min, int max, int min_offset) { int res = JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val)); if (res == 0) { if (*pres < min) { *pres += min_offset; if (*pres < min) *pres = min; } else { if (*pres > max) *pres = max; } } return res; } static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) { uint32_t tag; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: *pres = JS_VALUE_GET_INT(val); return 0; case JS_TAG_EXCEPTION: *pres = 0; return -1; case JS_TAG_FLOAT64: { double d = JS_VALUE_GET_FLOAT64(val); if (isnan(d)) { *pres = 0; } else { if (d < INT64_MIN) *pres = INT64_MIN; else if (d >= 0x1p63) /* must use INT64_MAX + 1 because INT64_MAX cannot be exactly represented as a double */ *pres = INT64_MAX; else *pres = (int64_t)d; } } return 0; default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { *pres = 0; return -1; } goto redo; } } int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValueConst val) { return JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val)); } int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValueConst val, int64_t min, int64_t max, int64_t neg_offset) { int res = JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val)); if (res == 0) { if (*pres < 0) *pres += neg_offset; if (*pres < min) *pres = min; else if (*pres > max) *pres = max; } return res; } /* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) in case of exception */ static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) { uint32_t tag; int64_t ret; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_VALUE_GET_INT(val); break; case JS_TAG_FLOAT64: { JSFloat64Union u; double d; int e; d = JS_VALUE_GET_FLOAT64(val); u.d = d; /* we avoid doing fmod(x, 2^64) */ e = (u.u64 >> 52) & 0x7ff; if (likely(e <= (1023 + 62))) { /* fast case */ ret = (int64_t)d; } else if (e <= (1023 + 62 + 53)) { uint64_t v; /* remainder modulo 2^64 */ v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); ret = v << ((e - 1023) - 52); /* take the sign into account */ if (u.u64 >> 63) ret = -ret; } else { ret = 0; /* also handles NaN and +inf */ } } break; default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { *pres = 0; return -1; } goto redo; } *pres = ret; return 0; } int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val) { return JS_ToInt64Free(ctx, pres, JS_DupValue(ctx, val)); } /* return (<0, 0) in case of exception */ static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) { uint32_t tag; int32_t ret; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_VALUE_GET_INT(val); break; case JS_TAG_FLOAT64: { JSFloat64Union u; double d; int e; d = JS_VALUE_GET_FLOAT64(val); u.d = d; /* we avoid doing fmod(x, 2^32) */ e = (u.u64 >> 52) & 0x7ff; if (likely(e <= (1023 + 30))) { /* fast case */ ret = (int32_t)d; } else if (e <= (1023 + 30 + 53)) { uint64_t v; /* remainder modulo 2^32 */ v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); v = v << ((e - 1023) - 52 + 32); ret = v >> 32; /* take the sign into account */ if (u.u64 >> 63) ret = -ret; } else { ret = 0; /* also handles NaN and +inf */ } } break; default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { *pres = 0; return -1; } goto redo; } *pres = ret; return 0; } int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val) { return JS_ToInt32Free(ctx, pres, JS_DupValue(ctx, val)); } static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) { return JS_ToInt32Free(ctx, (int32_t *)pres, val); } static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, JSValue val, BOOL is_array_ctor) { uint32_t tag, len; tag = JS_VALUE_GET_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: { int v; v = JS_VALUE_GET_INT(val); if (v < 0) goto fail; len = v; } break; default: if (JS_TAG_IS_FLOAT64(tag)) { double d; d = JS_VALUE_GET_FLOAT64(val); if (!(d >= 0 && d <= UINT32_MAX)) goto fail; len = (uint32_t)d; if (len != d) goto fail; } else { uint32_t len1; if (is_array_ctor) { val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) return -1; /* cannot recurse because val is a number */ if (JS_ToArrayLengthFree(ctx, &len, val, TRUE)) return -1; } else { /* legacy behavior: must do the conversion twice and compare */ if (JS_ToUint32(ctx, &len, val)) { JS_FreeValue(ctx, val); return -1; } val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) return -1; /* cannot recurse because val is a number */ if (JS_ToArrayLengthFree(ctx, &len1, val, FALSE)) return -1; if (len1 != len) { fail: JS_ThrowRangeError(ctx, "invalid array length"); return -1; } } } break; } *plen = len; return 0; } #define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val) { int64_t v; if (JS_ToInt64Sat(ctx, &v, val)) return -1; if (v < 0 || v > MAX_SAFE_INTEGER) { JS_ThrowRangeError(ctx, "invalid array index"); *plen = 0; return -1; } *plen = v; return 0; } /* convert a value to a length between 0 and MAX_SAFE_INTEGER. return -1 for exception */ static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, JSValue val) { int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); JS_FreeValue(ctx, val); return res; } static JSValue js_dtoa2(JSContext *ctx, double d, int radix, int n_digits, int flags) { char static_buf[128], *buf, *tmp_buf; int len, len_max; JSValue res; JSDTOATempMem dtoa_mem; len_max = js_dtoa_max_len(d, radix, n_digits, flags); /* longer buffer may be used if radix != 10 */ if (len_max > sizeof(static_buf) - 1) { tmp_buf = js_malloc(ctx, len_max + 1); if (!tmp_buf) return JS_EXCEPTION; buf = tmp_buf; } else { tmp_buf = NULL; buf = static_buf; } len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem); res = js_new_string8_len(ctx, buf, len); js_free(ctx, tmp_buf); return res; } static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey) { uint32_t tag; char buf[32]; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_STRING: return JS_DupValue(ctx, val); case JS_TAG_STRING_ROPE: return js_linearize_string_rope(ctx, JS_DupValue(ctx, val)); case JS_TAG_INT: { size_t len; len = i32toa(buf, JS_VALUE_GET_INT(val)); return js_new_string8_len(ctx, buf, len); } break; case JS_TAG_BOOL: return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? JS_ATOM_true : JS_ATOM_false); case JS_TAG_NULL: return JS_AtomToString(ctx, JS_ATOM_null); case JS_TAG_EXCEPTION: return JS_EXCEPTION; case JS_TAG_OBJECT: { JSValue val1, ret; val1 = JS_ToPrimitive(ctx, val, HINT_STRING); if (JS_IsException(val1)) return val1; ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey); JS_FreeValue(ctx, val1); return ret; } break; case JS_TAG_FUNCTION_BYTECODE: return js_new_string8(ctx, "[function bytecode]"); case JS_TAG_SYMBOL: if (is_ToPropertyKey) { return JS_DupValue(ctx, val); } else { return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); } case JS_TAG_FLOAT64: return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, JS_DTOA_FORMAT_FREE); default: return js_new_string8(ctx, "[unsupported type]"); } } JSValue JS_ToString(JSContext *ctx, JSValueConst val) { return JS_ToStringInternal(ctx, val, FALSE); } static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) { JSValue ret; ret = JS_ToString(ctx, val); JS_FreeValue(ctx, val); return ret; } static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) { if (JS_IsNull(val) || JS_IsNull(val)) return JS_ToStringFree(ctx, val); return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); } JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val) { int tag = JS_VALUE_GET_TAG(val); /* Objects become their cached object-key symbol (identity-based key) */ if (tag == JS_TAG_OBJECT) { return js_get_object_key_symbol(ctx, JS_VALUE_GET_OBJ(val)); } return JS_ToStringInternal(ctx, val, TRUE); } static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val) { uint32_t tag = JS_VALUE_GET_TAG(val); if (tag == JS_TAG_NULL) return JS_ThrowTypeError(ctx, "null is forbidden"); return JS_ToString(ctx, val); } static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1) { JSValue val; JSString *p; int i; uint32_t c; StringBuffer b_s, *b = &b_s; char buf[16]; val = JS_ToStringCheckObject(ctx, val1); if (JS_IsException(val)) return val; p = JS_VALUE_GET_STRING(val); if (string_buffer_init(ctx, b, p->len + 2)) goto fail; if (string_buffer_putc8(b, '\"')) goto fail; for(i = 0; i < p->len; ) { c = string_getc(p, &i); switch(c) { case '\t': c = 't'; goto quote; case '\r': c = 'r'; goto quote; case '\n': c = 'n'; goto quote; case '\b': c = 'b'; goto quote; case '\f': c = 'f'; goto quote; case '\"': case '\\': quote: if (string_buffer_putc8(b, '\\')) goto fail; if (string_buffer_putc8(b, c)) goto fail; break; default: if (c < 32 || is_surrogate(c)) { snprintf(buf, sizeof(buf), "\\u%04x", c); if (string_buffer_puts8(b, buf)) goto fail; } else { if (string_buffer_putc(b, c)) goto fail; } break; } } if (string_buffer_putc8(b, '\"')) goto fail; JS_FreeValue(ctx, val); return string_buffer_end(b); fail: JS_FreeValue(ctx, val); string_buffer_free(b); return JS_EXCEPTION; } #define JS_PRINT_MAX_DEPTH 8 typedef struct { JSRuntime *rt; JSContext *ctx; /* may be NULL */ JSPrintValueOptions options; JSPrintValueWrite *write_func; void *write_opaque; int level; JSObject *print_stack[JS_PRINT_MAX_DEPTH]; /* level values */ } JSPrintValueState; static void js_print_value(JSPrintValueState *s, JSValueConst val); static void js_putc(JSPrintValueState *s, char c) { s->write_func(s->write_opaque, &c, 1); } static void js_puts(JSPrintValueState *s, const char *str) { s->write_func(s->write_opaque, str, strlen(str)); } static void __attribute__((format(printf, 2, 3))) js_printf(JSPrintValueState *s, const char *fmt, ...) { va_list ap; char buf[256]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); s->write_func(s->write_opaque, buf, strlen(buf)); } static void js_print_float64(JSPrintValueState *s, double d) { JSDTOATempMem dtoa_mem; char buf[32]; int len; len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &dtoa_mem); s->write_func(s->write_opaque, buf, len); } static uint32_t js_string_get_length(JSValueConst val) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { JSString *p = JS_VALUE_GET_STRING(val); return p->len; } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { JSStringRope *r = JS_VALUE_GET_PTR(val); return r->len; } else { return 0; } } static void js_dump_char(JSPrintValueState *s, int c, int sep) { if (c == sep || c == '\\') { js_putc(s, '\\'); js_putc(s, c); } else if (c >= ' ' && c <= 126) { js_putc(s, c); } else if (c == '\n') { js_putc(s, '\\'); js_putc(s, 'n'); } else { js_printf(s, "\\u%04x", c); } } static void js_print_string_rec(JSPrintValueState *s, JSValueConst val, int sep, uint32_t pos) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { JSString *p = JS_VALUE_GET_STRING(val); uint32_t i, len; if (pos < s->options.max_string_length) { len = min_uint32(p->len, s->options.max_string_length - pos); for(i = 0; i < len; i++) { js_dump_char(s, string_get(p, i), sep); } } } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { JSStringRope *r = JS_VALUE_GET_PTR(val); js_print_string_rec(s, r->left, sep, pos); js_print_string_rec(s, r->right, sep, pos + js_string_get_length(r->left)); } else { js_printf(s, "", (int)JS_VALUE_GET_TAG(val)); } } static void js_print_string(JSPrintValueState *s, JSValueConst val) { int sep; if (s->options.raw_dump && JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { JSString *p = JS_VALUE_GET_STRING(val); js_printf(s, "%d", p->header.ref_count); sep = (p->header.ref_count == 1) ? '\"' : '\''; } else { sep = '\"'; } js_putc(s, sep); js_print_string_rec(s, val, sep, 0); js_putc(s, sep); if (js_string_get_length(val) > s->options.max_string_length) { uint32_t n = js_string_get_length(val) - s->options.max_string_length; js_printf(s, "... %u more character%s", n, n > 1 ? "s" : ""); } } static void js_print_raw_string2(JSPrintValueState *s, JSValueConst val, BOOL remove_last_lf) { const char *cstr; size_t len; cstr = JS_ToCStringLen(s->ctx, &len, val); if (cstr) { if (remove_last_lf && len > 0 && cstr[len - 1] == '\n') len--; s->write_func(s->write_opaque, cstr, len); JS_FreeCString(s->ctx, cstr); } } static void js_print_raw_string(JSPrintValueState *s, JSValueConst val) { js_print_raw_string2(s, val, FALSE); } static BOOL is_ascii_ident(const JSString *p) { int i, c; if (p->len == 0) return FALSE; for(i = 0; i < p->len; i++) { c = string_get(p, i); if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) return FALSE; } return TRUE; } static void js_print_atom(JSPrintValueState *s, JSAtom atom) { int i; if (__JS_AtomIsTaggedInt(atom)) { js_printf(s, "%u", __JS_AtomToUInt32(atom)); } else if (atom == JS_ATOM_NULL) { js_puts(s, ""); } else { assert(atom < s->rt->atom_size); JSString *p; p = s->rt->atom_array[atom]; if (is_ascii_ident(p)) { for(i = 0; i < p->len; i++) { js_putc(s, string_get(p, i)); } } else { js_putc(s, '"'); for(i = 0; i < p->len; i++) { js_dump_char(s, string_get(p, i), '\"'); } js_putc(s, '"'); } } } /* return 0 if invalid length */ static uint32_t js_print_array_get_length(JSObject *p) { JSProperty *pr; JSShapeProperty *prs; JSValueConst val; prs = find_own_property(&pr, p, JS_ATOM_length); if (!prs) return 0; if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) return 0; val = pr->u.value; switch(JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_INT: return JS_VALUE_GET_INT(val); case JS_TAG_FLOAT64: return (uint32_t)JS_VALUE_GET_FLOAT64(val); default: return 0; } } static void js_print_comma(JSPrintValueState *s, int *pcomma_state) { switch(*pcomma_state) { case 0: break; case 1: js_printf(s, ", "); break; case 2: js_printf(s, " { "); break; } *pcomma_state = 1; } static void js_print_more_items(JSPrintValueState *s, int *pcomma_state, uint32_t n) { js_print_comma(s, pcomma_state); js_printf(s, "... %u more item%s", n, n > 1 ? "s" : ""); } static void js_print_object(JSPrintValueState *s, JSObject *p) { JSRuntime *rt = s->rt; JSShape *sh; JSShapeProperty *prs; JSProperty *pr; int comma_state; BOOL is_array; uint32_t i; comma_state = 0; is_array = FALSE; if (p->class_id == JS_CLASS_ARRAY) { is_array = TRUE; js_printf(s, "[ "); /* XXX: print array like properties even if not fast array */ if (p->fast_array) { uint32_t len, n, len1; len = js_print_array_get_length(p); len1 = min_uint32(p->u.array.count, s->options.max_item_count); for(i = 0; i < len1; i++) { js_print_comma(s, &comma_state); js_print_value(s, p->u.array.u.values[i]); } if (len1 < p->u.array.count) js_print_more_items(s, &comma_state, p->u.array.count - len1); if (p->u.array.count < len) { n = len - p->u.array.count; js_print_comma(s, &comma_state); js_printf(s, "<%u empty item%s>", n, n > 1 ? "s" : ""); } } } else if (p->class_id == JS_CLASS_BYTECODE_FUNCTION || rt->class_array[p->class_id].call != NULL) { js_printf(s, "[Function"); /* XXX: allow dump without ctx */ if (!s->options.raw_dump && s->ctx) { const char *func_name_str; js_putc(s, ' '); func_name_str = get_func_name(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p)); if (!func_name_str || func_name_str[0] == '\0') js_puts(s, "(anonymous)"); else js_puts(s, func_name_str); JS_FreeCString(s->ctx, func_name_str); } js_printf(s, "]"); comma_state = 2; } else if (p->class_id == JS_CLASS_REGEXP && s->ctx && !s->options.raw_dump) { JSValue str = js_regexp_toString(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL); if (JS_IsException(str)) goto default_obj; js_print_raw_string(s, str); JS_FreeValueRT(s->rt, str); comma_state = 2; } else if (p->class_id == JS_CLASS_ERROR && s->ctx && !s->options.raw_dump) { JSValue str = js_error_toString(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL); if (JS_IsException(str)) goto default_obj; js_print_raw_string(s, str); JS_FreeValueRT(s->rt, str); /* dump the stack if present */ str = JS_GetProperty(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_stack); if (JS_IsString(str)) { js_putc(s, '\n'); /* XXX: should remove the last '\n' in stack as v8. SpiderMonkey does not do it */ js_print_raw_string2(s, str, TRUE); } JS_FreeValueRT(s->rt, str); comma_state = 2; } else { default_obj: if (p->class_id != JS_CLASS_OBJECT) { js_print_atom(s, rt->class_array[p->class_id].class_name); js_printf(s, " "); } js_printf(s, "{ "); } sh = p->shape; /* the shape can be NULL while freeing an object */ if (sh) { uint32_t j; j = 0; for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { if (prs->atom != JS_ATOM_NULL) { if (!(prs->flags & JS_PROP_ENUMERABLE) && !s->options.show_hidden) { continue; } if (j < s->options.max_item_count) { pr = &p->prop[i]; js_print_comma(s, &comma_state); js_print_atom(s, prs->atom); js_printf(s, ": "); /* XXX: autoinit property */ if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { if (s->options.raw_dump) { js_printf(s, "[Getter %p Setter %p]", pr->u.getset.getter, pr->u.getset.setter); } else { if (pr->u.getset.getter && pr->u.getset.setter) { js_printf(s, "[Getter/Setter]"); } else if (pr->u.getset.setter) { js_printf(s, "[Setter]"); } else { js_printf(s, "[Getter]"); } } } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { if (s->options.raw_dump) { js_printf(s, "[varref %p]", (void *)pr->u.var_ref); } else { js_print_value(s, *pr->u.var_ref->pvalue); } } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { if (s->options.raw_dump) { js_printf(s, "[autoinit %p %d %p]", (void *)js_autoinit_get_realm(pr), js_autoinit_get_id(pr), (void *)pr->u.init.opaque); } else { /* XXX: could autoinit but need to restart the iteration */ js_printf(s, "[autoinit]"); } } else { js_print_value(s, pr->u.value); } } j++; } } if (j > s->options.max_item_count) js_print_more_items(s, &comma_state, j - s->options.max_item_count); } if (s->options.raw_dump && js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b = p->u.func.function_bytecode; if (b->closure_var_count) { JSVarRef **var_refs; var_refs = p->u.func.var_refs; js_print_comma(s, &comma_state); js_printf(s, "[[Closure]]: ["); for(i = 0; i < b->closure_var_count; i++) { if (i != 0) js_printf(s, ", "); js_print_value(s, var_refs[i]->value); } js_printf(s, " ]"); } } if (!is_array) { if (comma_state != 2) { js_printf(s, " }"); } } else { js_printf(s, " ]"); } } static int js_print_stack_index(JSPrintValueState *s, JSObject *p) { int i; for(i = 0; i < s->level; i++) if (s->print_stack[i] == p) return i; return -1; } static void js_print_value(JSPrintValueState *s, JSValueConst val) { uint32_t tag = JS_VALUE_GET_NORM_TAG(val); const char *str; switch(tag) { case JS_TAG_INT: js_printf(s, "%d", JS_VALUE_GET_INT(val)); break; case JS_TAG_BOOL: if (JS_VALUE_GET_BOOL(val)) str = "true"; else str = "false"; goto print_str; case JS_TAG_NULL: str = "null"; goto print_str; case JS_TAG_EXCEPTION: str = "exception"; goto print_str; case JS_TAG_UNINITIALIZED: str = "uninitialized"; goto print_str; print_str: js_puts(s, str); break; case JS_TAG_FLOAT64: js_print_float64(s, JS_VALUE_GET_FLOAT64(val)); break; case JS_TAG_STRING: case JS_TAG_STRING_ROPE: if (s->options.raw_dump && tag == JS_TAG_STRING_ROPE) { JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); js_printf(s, "[rope len=%d depth=%d]", r->len, r->depth); } else { js_print_string(s, val); } break; case JS_TAG_FUNCTION_BYTECODE: { JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); js_puts(s, "[bytecode "); js_print_atom(s, b->func_name); js_putc(s, ']'); } break; case JS_TAG_OBJECT: { JSObject *p = JS_VALUE_GET_OBJ(val); int idx; idx = js_print_stack_index(s, p); if (idx >= 0) { js_printf(s, "[circular %d]", idx); } else if (s->level < s->options.max_depth) { s->print_stack[s->level++] = p; js_print_object(s, JS_VALUE_GET_OBJ(val)); s->level--; } else { JSAtom atom = s->rt->class_array[p->class_id].class_name; js_putc(s, '['); js_print_atom(s, atom); if (s->options.raw_dump) { js_printf(s, " %p", (void *)p); } js_putc(s, ']'); } } break; case JS_TAG_SYMBOL: { JSAtomStruct *p = JS_VALUE_GET_PTR(val); js_puts(s, "Symbol("); js_print_atom(s, js_get_atom_index(s->rt, p)); js_putc(s, ')'); } break; default: js_printf(s, "[unknown tag %d]", tag); break; } } void JS_PrintValueSetDefaultOptions(JSPrintValueOptions *options) { memset(options, 0, sizeof(*options)); options->max_depth = 2; options->max_string_length = 1000; options->max_item_count = 100; } static void JS_PrintValueInternal(JSRuntime *rt, JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValueConst val, const JSPrintValueOptions *options) { JSPrintValueState ss, *s = &ss; if (options) s->options = *options; else JS_PrintValueSetDefaultOptions(&s->options); if (s->options.max_depth <= 0) s->options.max_depth = JS_PRINT_MAX_DEPTH; else s->options.max_depth = min_int(s->options.max_depth, JS_PRINT_MAX_DEPTH); if (s->options.max_string_length == 0) s->options.max_string_length = UINT32_MAX; if (s->options.max_item_count == 0) s->options.max_item_count = UINT32_MAX; s->rt = rt; s->ctx = ctx; s->write_func = write_func; s->write_opaque = write_opaque; s->level = 0; js_print_value(s, val); } void JS_PrintValueRT(JSRuntime *rt, JSPrintValueWrite *write_func, void *write_opaque, JSValueConst val, const JSPrintValueOptions *options) { JS_PrintValueInternal(rt, NULL, write_func, write_opaque, val, options); } void JS_PrintValue(JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValueConst val, const JSPrintValueOptions *options) { JS_PrintValueInternal(ctx->rt, ctx, write_func, write_opaque, val, options); } static void js_dump_value_write(void *opaque, const char *buf, size_t len) { FILE *fo = opaque; fwrite(buf, 1, len, fo); } static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) { JSPrintValueState ss, *s = &ss; memset(s, 0, sizeof(*s)); s->rt = ctx->rt; s->ctx = ctx; s->write_func = js_dump_value_write; s->write_opaque = stdout; js_print_atom(s, atom); } static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val) { printf("%s=", str); JS_PrintValue(ctx, js_dump_value_write, stdout, val, NULL); printf("\n"); } static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val) { printf("%s=", str); JS_PrintValueRT(rt, js_dump_value_write, stdout, val, NULL); printf("\n"); } static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) { printf("%14s %4s %4s %14s %s\n", "ADDRESS", "REFS", "SHRF", "PROTO", "CONTENT"); } /* for debug only: dump an object without side effect */ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) { JSShape *sh; JSPrintValueOptions options; /* XXX: should encode atoms with special characters */ sh = p->shape; /* the shape can be NULL while freeing an object */ printf("%14p %4d ", (void *)p, p->header.ref_count); if (sh) { printf("%3d%c %14p ", sh->header.ref_count, " *"[sh->is_hashed], (void *)sh->proto); } else { printf("%3s %14s ", "-", "-"); } JS_PrintValueSetDefaultOptions(&options); options.max_depth = 1; options.show_hidden = TRUE; options.raw_dump = TRUE; JS_PrintValueRT(rt, js_dump_value_write, stdout, JS_MKPTR(JS_TAG_OBJECT, p), &options); printf("\n"); } static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) { if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { JS_DumpObject(rt, (JSObject *)p); } else { printf("%14p %4d ", (void *)p, p->ref_count); switch(p->gc_obj_type) { case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: printf("[function bytecode]"); break; case JS_GC_OBJ_TYPE_SHAPE: printf("[shape]"); break; case JS_GC_OBJ_TYPE_VAR_REF: printf("[var_ref]"); break; case JS_GC_OBJ_TYPE_JS_CONTEXT: printf("[js_context]"); break; default: printf("[unknown %d]", p->gc_obj_type); break; } printf("\n"); } } /* return -1 if exception (proxy case) or TRUE/FALSE */ // TODO: should take flags to make proxy resolution and exceptions optional int JS_IsArray(JSContext *ctx, JSValueConst val) { if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(val); return p->class_id == JS_CLASS_ARRAY; } else { return FALSE; } } static double js_pow(double a, double b) { if (unlikely(!isfinite(b)) && fabs(a) == 1) { /* not compatible with IEEE 754 */ return JS_FLOAT64_NAN; } else { return pow(a, b); } } static no_inline __exception int js_unary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1; int v; uint32_t tag; op1 = sp[-1]; /* fast path for float64 */ if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) goto handle_float64; op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) goto exception; tag = JS_VALUE_GET_TAG(op1); switch(tag) { case JS_TAG_INT: { int64_t v64; v64 = JS_VALUE_GET_INT(op1); switch(op) { case OP_inc: case OP_dec: v = 2 * (op - OP_dec) - 1; v64 += v; break; case OP_plus: break; case OP_neg: if (v64 == 0) { sp[-1] = __JS_NewFloat64(ctx, -0.0); return 0; } else { v64 = -v64; } break; default: abort(); } sp[-1] = JS_NewInt64(ctx, v64); } break; default: handle_float64: { double d; d = JS_VALUE_GET_FLOAT64(op1); switch(op) { case OP_inc: case OP_dec: v = 2 * (op - OP_dec) - 1; d += v; break; case OP_plus: break; case OP_neg: d = -d; break; default: abort(); } sp[-1] = __JS_NewFloat64(ctx, d); } break; } return 0; exception: sp[-1] = JS_NULL; return -1; } static __exception int js_post_inc_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1; /* XXX: allow custom operators */ op1 = sp[-1]; op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) { sp[-1] = JS_NULL; return -1; } sp[-1] = op1; sp[0] = JS_DupValue(ctx, op1); return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); } static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) { JSValue op1; op1 = sp[-1]; op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) goto exception; int32_t v1; if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) goto exception; sp[-1] = JS_NewInt32(ctx, ~v1); return 0; exception: sp[-1] = JS_NULL; return -1; } static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2; uint32_t tag1, tag2; double d1, d2; op1 = sp[-2]; op2 = sp[-1]; tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); /* fast path for float operations */ if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { d1 = JS_VALUE_GET_FLOAT64(op1); d2 = JS_VALUE_GET_FLOAT64(op2); goto handle_float64; } if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { if(tag1 == JS_TAG_INT) d1 = (double)JS_VALUE_GET_INT(op1); else d1 = JS_VALUE_GET_FLOAT64(op1); if(tag2 == JS_TAG_INT) d2 = (double)JS_VALUE_GET_INT(op2); else d2 = JS_VALUE_GET_FLOAT64(op2); goto handle_float64; } op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); goto exception; } op2 = JS_ToNumericFree(ctx, op2); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); goto exception; } tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { int32_t v1, v2; int64_t v; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); switch(op) { case OP_sub: v = (int64_t)v1 - (int64_t)v2; break; case OP_mul: v = (int64_t)v1 * (int64_t)v2; if (v == 0 && (v1 | v2) < 0) { sp[-2] = __JS_NewFloat64(ctx, -0.0); return 0; } break; case OP_div: sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2); return 0; case OP_mod: if (v1 < 0 || v2 <= 0) { sp[-2] = JS_NewFloat64(ctx, fmod(v1, v2)); return 0; } else { v = (int64_t)v1 % (int64_t)v2; } break; case OP_pow: sp[-2] = JS_NewFloat64(ctx, js_pow(v1, v2)); return 0; default: abort(); } sp[-2] = JS_NewInt64(ctx, v); } else { double dr; /* float64 result */ if (JS_ToFloat64Free(ctx, &d1, op1)) { JS_FreeValue(ctx, op2); goto exception; } if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; handle_float64: switch(op) { case OP_sub: dr = d1 - d2; break; case OP_mul: dr = d1 * d2; break; case OP_div: dr = d1 / d2; break; case OP_mod: dr = fmod(d1, d2); break; case OP_pow: dr = js_pow(d1, d2); break; default: abort(); } sp[-2] = __JS_NewFloat64(ctx, dr); } return 0; exception: sp[-2] = JS_NULL; sp[-1] = JS_NULL; return -1; } static inline BOOL tag_is_string(uint32_t tag) { return tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE; } static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1 = sp[-2], op2 = sp[-1]; uint32_t tag1 = JS_VALUE_GET_NORM_TAG(op1); uint32_t tag2 = JS_VALUE_GET_NORM_TAG(op2); int res; /* string <=> string */ if (tag_is_string(tag1) && tag_is_string(tag2)) { if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) res = js_string_compare(ctx, JS_VALUE_GET_STRING(op1), JS_VALUE_GET_STRING(op2)); else res = js_string_rope_compare(ctx, op1, op2, FALSE); switch(op) { case OP_lt: res = (res < 0); break; case OP_lte: res = (res <= 0); break; case OP_gt: res = (res > 0); break; default: res = (res >= 0); break; } /* number <=> number */ } else if ((tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) { double d1 = (tag1 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64(op1) : (double)JS_VALUE_GET_INT(op1)); double d2 = (tag2 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64(op2) : (double)JS_VALUE_GET_INT(op2)); switch(op) { case OP_lt: res = (d1 < d2); break; case OP_lte: res = (d1 <= d2); break; case OP_gt: res = (d1 > d2); break; default: res = (d1 >= d2); break; } /* anything else → TypeError */ } else { JS_ThrowTypeError(ctx, "Relational operators only supported on two strings or two numbers"); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); goto exception; } /* free the two input values and push the result */ JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); sp[-2] = JS_NewBool(ctx, res); return 0; exception: sp[-2] = JS_NULL; sp[-1] = JS_NULL; return -1; } static BOOL tag_is_number(uint32_t tag) { return (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64); } static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq) { JSValue op1, op2; int res; uint32_t tag1, tag2; op1 = sp[-2]; op2 = sp[-1]; redo: tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); if (tag_is_number(tag1) && tag_is_number(tag2)) { if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); } else if ((tag1 == JS_TAG_FLOAT64 && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || (tag2 == JS_TAG_FLOAT64 && (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { double d1, d2; if (tag1 == JS_TAG_FLOAT64) { d1 = JS_VALUE_GET_FLOAT64(op1); } else { d1 = JS_VALUE_GET_INT(op1); } if (tag2 == JS_TAG_FLOAT64) { d2 = JS_VALUE_GET_FLOAT64(op2); } else { d2 = JS_VALUE_GET_INT(op2); } res = (d1 == d2); } } else if (tag1 == tag2) { res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } else if (tag1 == JS_TAG_NULL && tag2 == JS_TAG_NULL) { res = TRUE; } else if (tag_is_string(tag1) && tag_is_string(tag2)) { /* needed when comparing strings and ropes */ res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } else if ((tag_is_string(tag1) && tag_is_number(tag2)) || (tag_is_string(tag2) && tag_is_number(tag1))) { op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); goto exception; } op2 = JS_ToNumericFree(ctx, op2); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); goto exception; } res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } else if (tag1 == JS_TAG_BOOL) { op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1)); goto redo; } else if (tag2 == JS_TAG_BOOL) { op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2)); goto redo; } else if ((tag1 == JS_TAG_OBJECT && (tag_is_number(tag2) || tag_is_string(tag2) || tag2 == JS_TAG_SYMBOL)) || (tag2 == JS_TAG_OBJECT && (tag_is_number(tag1) || tag_is_string(tag1) || tag1 == JS_TAG_SYMBOL))) { op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); goto exception; } op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); goto exception; } goto redo; } else { res = FALSE; JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); } done: sp[-2] = JS_NewBool(ctx, res ^ is_neq); return 0; exception: sp[-2] = JS_NULL; sp[-1] = JS_NULL; return -1; } /* XXX: Should take JSValueConst arguments */ static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, JSStrictEqModeEnum eq_mode) { BOOL res; int tag1, tag2; double d1, d2; tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); switch(tag1) { case JS_TAG_BOOL: if (tag1 != tag2) { res = FALSE; } else { res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); goto done_no_free; } break; case JS_TAG_NULL: res = (tag1 == tag2); break; case JS_TAG_STRING: case JS_TAG_STRING_ROPE: { if (!tag_is_string(tag2)) { res = FALSE; } else if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { res = (js_string_compare(ctx, JS_VALUE_GET_STRING(op1), JS_VALUE_GET_STRING(op2)) == 0); } else { res = (js_string_rope_compare(ctx, op1, op2, TRUE) == 0); } } break; case JS_TAG_SYMBOL: { JSAtomStruct *p1, *p2; if (tag1 != tag2) { res = FALSE; } else { p1 = JS_VALUE_GET_PTR(op1); p2 = JS_VALUE_GET_PTR(op2); res = (p1 == p2); } } break; case JS_TAG_OBJECT: if (tag1 != tag2) res = FALSE; else res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); break; case JS_TAG_INT: d1 = JS_VALUE_GET_INT(op1); if (tag2 == JS_TAG_INT) { d2 = JS_VALUE_GET_INT(op2); goto number_test; } else if (tag2 == JS_TAG_FLOAT64) { d2 = JS_VALUE_GET_FLOAT64(op2); goto number_test; } else { res = FALSE; } break; case JS_TAG_FLOAT64: d1 = JS_VALUE_GET_FLOAT64(op1); if (tag2 == JS_TAG_FLOAT64) { d2 = JS_VALUE_GET_FLOAT64(op2); } else if (tag2 == JS_TAG_INT) { d2 = JS_VALUE_GET_INT(op2); } else { res = FALSE; break; } number_test: if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { JSFloat64Union u1, u2; /* NaN is not always normalized, so this test is necessary */ if (isnan(d1) || isnan(d2)) { res = isnan(d1) == isnan(d2); } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { res = (d1 == d2); /* +0 == -0 */ } else { u1.d = d1; u2.d = d2; res = (u1.u64 == u2.u64); /* +0 != -0 */ } } else { res = (d1 == d2); /* if NaN return false and +0 == -0 */ } goto done_no_free; default: res = FALSE; break; } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); done_no_free: return res; } static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2) { return js_strict_eq2(ctx, JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), JS_EQ_STRICT); } BOOL JS_StrictEq(JSContext *ctx, JSValueConst op1, JSValueConst op2) { return js_strict_eq(ctx, op1, op2); } static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) { return js_strict_eq2(ctx, JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), JS_EQ_SAME_VALUE); } BOOL JS_SameValue(JSContext *ctx, JSValueConst op1, JSValueConst op2) { return js_same_value(ctx, op1, op2); } static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) { return js_strict_eq2(ctx, JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), JS_EQ_SAME_VALUE_ZERO); } BOOL JS_SameValueZero(JSContext *ctx, JSValueConst op1, JSValueConst op2) { return js_same_value_zero(ctx, op1, op2); } static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq) { BOOL res; res = js_strict_eq2(ctx, sp[-2], sp[-1], JS_EQ_STRICT); sp[-2] = JS_NewBool(ctx, res ^ is_neq); return 0; } static __exception int js_operator_in(JSContext *ctx, JSValue *sp) { JSValue op1, op2; JSAtom atom; int ret; op1 = sp[-2]; op2 = sp[-1]; if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { JS_ThrowTypeError(ctx, "invalid 'in' operand"); return -1; } atom = JS_ValueToAtom(ctx, op1); if (unlikely(atom == JS_ATOM_NULL)) return -1; ret = JS_HasProperty(ctx, op2, atom); JS_FreeAtom(ctx, atom); if (ret < 0) return -1; JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); sp[-2] = JS_NewBool(ctx, ret); return 0; } static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) { JSValue op1, op2; JSAtom atom; int ret; op1 = sp[-2]; op2 = sp[-1]; atom = JS_ValueToAtom(ctx, op2); if (unlikely(atom == JS_ATOM_NULL)) return -1; ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); JS_FreeAtom(ctx, atom); if (unlikely(ret < 0)) return -1; JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); sp[-2] = JS_NewBool(ctx, ret); return 0; } /* XXX: not 100% compatible, but mozilla seems to use a similar implementation to ensure that caller in non strict mode does not throw (ES5 compatibility) */ static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ThrowTypeError(ctx, "invalid property access"); } #define GLOBAL_VAR_OFFSET 0x40000000 #define ARGUMENT_VAR_OFFSET 0x20000000 static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValueConst *argv) { JSValue val; int i, ret; val = JS_NewArray(ctx); if (JS_IsException(val)) return val; for (i = first; i < argc; i++) { ret = JS_DefinePropertyValueUint32(ctx, val, i - first, JS_DupValue(ctx, argv[i]), JS_PROP_C_W_E); if (ret < 0) { JS_FreeValue(ctx, val); return JS_EXCEPTION; } } return val; } static BOOL js_is_fast_array(JSContext *ctx, JSValueConst obj) { /* Try and handle fast arrays explicitly */ if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(obj); if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { return TRUE; } } return FALSE; } /* Access an Array's internal JSValue array if available */ static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, JSValue **arrpp, uint32_t *countp) { /* Try and handle fast arrays explicitly */ if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(obj); if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { *countp = p->u.array.count; *arrpp = p->u.array.u.values; return TRUE; } } return FALSE; } static __exception int JS_CopyDataProperties(JSContext *ctx, JSValueConst target, JSValueConst source, JSValueConst excluded, BOOL setprop) { JSPropertyEnum *tab_atom; JSValue val; uint32_t i, tab_atom_count; JSObject *p; JSObject *pexcl = NULL; int ret, gpn_flags; JSPropertyDescriptor desc; BOOL is_enumerable; if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) return 0; if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) pexcl = JS_VALUE_GET_OBJ(excluded); p = JS_VALUE_GET_OBJ(source); gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, gpn_flags)) return -1; for (i = 0; i < tab_atom_count; i++) { if (pexcl) { ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); if (ret) { if (ret < 0) goto exception; continue; } } if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { /* test if the property is enumerable */ ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom); if (ret < 0) goto exception; if (!ret) continue; is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0; js_free_desc(ctx, &desc); if (!is_enumerable) continue; } val = JS_GetProperty(ctx, source, tab_atom[i].atom); if (JS_IsException(val)) goto exception; if (setprop) ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); else ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, JS_PROP_C_W_E); if (ret < 0) goto exception; } JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); return 0; exception: JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); return -1; } /* only valid inside C functions */ static JSValueConst JS_GetActiveFunction(JSContext *ctx) { return ctx->rt->current_stack_frame->cur_func; } static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, BOOL is_arg) { JSVarRef *var_ref; struct list_head *el; JSValue *pvalue; if (is_arg) pvalue = &sf->arg_buf[var_idx]; else pvalue = &sf->var_buf[var_idx]; list_for_each(el, &sf->var_ref_list) { var_ref = list_entry(el, JSVarRef, var_ref_link); if (var_ref->pvalue == pvalue) { var_ref->header.ref_count++; return var_ref; } } /* create a new one */ var_ref = js_malloc(ctx, sizeof(JSVarRef)); if (!var_ref) return NULL; var_ref->header.ref_count = 1; add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); var_ref->is_detached = FALSE; list_add_tail(&var_ref->var_ref_link, &sf->var_ref_list); var_ref->pvalue = pvalue; return var_ref; } static JSValue js_closure2(JSContext *ctx, JSValue func_obj, JSFunctionBytecode *b, JSVarRef **cur_var_refs, JSStackFrame *sf) { JSObject *p; JSVarRef **var_refs; int i; p = JS_VALUE_GET_OBJ(func_obj); p->u.func.function_bytecode = b; p->u.func.var_refs = NULL; if (b->closure_var_count) { var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); if (!var_refs) goto fail; p->u.func.var_refs = var_refs; for(i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; JSVarRef *var_ref; if (cv->is_local) { /* reuse the existing variable reference if it already exists */ var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg); if (!var_ref) goto fail; } else { var_ref = cur_var_refs[cv->var_idx]; var_ref->header.ref_count++; } var_refs[i] = var_ref; } } return func_obj; fail: /* bfunc is freed when func_obj is freed */ JS_FreeValue(ctx, func_obj); return JS_EXCEPTION; } static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) { JSValue obj, this_val; int ret; this_val = JS_MKPTR(JS_TAG_OBJECT, p); obj = JS_NewObject(ctx); if (JS_IsException(obj)) return JS_EXCEPTION; set_cycle_flag(ctx, obj); set_cycle_flag(ctx, this_val); ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, JS_DupValue(ctx, this_val), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); if (ret < 0) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } return obj; } static JSValue js_closure(JSContext *ctx, JSValue bfunc, JSVarRef **cur_var_refs, JSStackFrame *sf) { JSFunctionBytecode *b; JSValue func_obj; JSAtom name_atom; b = JS_VALUE_GET_PTR(bfunc); func_obj = JS_NewObjectClass(ctx, JS_CLASS_BYTECODE_FUNCTION); if (JS_IsException(func_obj)) { JS_FreeValue(ctx, bfunc); return JS_EXCEPTION; } func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf); if (JS_IsException(func_obj)) { /* bfunc has been freed */ goto fail; } name_atom = b->func_name; if (name_atom == JS_ATOM_NULL) name_atom = JS_ATOM_empty_string; js_function_set_properties(ctx, func_obj, name_atom, b->defined_arg_count); return func_obj; fail: /* bfunc is freed when func_obj is freed */ JS_FreeValue(ctx, func_obj); return JS_EXCEPTION; } /* Classes are not supported - js_op_define_class removed */ static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) { struct list_head *el, *el1; JSVarRef *var_ref; list_for_each_safe(el, el1, &sf->var_ref_list) { var_ref = list_entry(el, JSVarRef, var_ref_link); /* no need to unlink var_ref->var_ref_link as the list is never used afterwards */ var_ref->value = JS_DupValueRT(rt, *var_ref->pvalue); var_ref->pvalue = &var_ref->value; /* the reference is no longer to a local variable */ var_ref->is_detached = TRUE; } } static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int var_idx) { JSValue *pvalue; struct list_head *el, *el1; JSVarRef *var_ref; pvalue = &sf->var_buf[var_idx]; list_for_each_safe(el, el1, &sf->var_ref_list) { var_ref = list_entry(el, JSVarRef, var_ref_link); if (var_ref->pvalue == pvalue) { list_del(&var_ref->var_ref_link); var_ref->value = JS_DupValue(ctx, *var_ref->pvalue); var_ref->pvalue = &var_ref->value; /* the reference is no longer to a local variable */ var_ref->is_detached = TRUE; } } } #define JS_CALL_FLAG_COPY_ARGV (1 << 1) static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSRuntime *rt = ctx->rt; JSCFunctionType func; JSObject *p; JSStackFrame sf_s, *sf = &sf_s, *prev_sf; JSValue ret_val; JSValueConst *arg_buf; int arg_count, i; JSCFunctionEnum cproto; p = JS_VALUE_GET_OBJ(func_obj); cproto = p->u.cfunc.cproto; arg_count = p->u.cfunc.length; /* better to always check stack overflow */ if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) return JS_ThrowStackOverflow(ctx); prev_sf = rt->current_stack_frame; sf->prev_frame = prev_sf; rt->current_stack_frame = sf; ctx = p->u.cfunc.realm; /* change the current realm */ sf->js_mode = 0; sf->cur_func = (JSValue)func_obj; sf->arg_count = argc; arg_buf = argv; if (unlikely(argc < arg_count)) { /* ensure that at least argc_count arguments are readable */ arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); for(i = 0; i < argc; i++) arg_buf[i] = argv[i]; for(i = argc; i < arg_count; i++) arg_buf[i] = JS_NULL; sf->arg_count = arg_count; } sf->arg_buf = (JSValue*)arg_buf; func = p->u.cfunc.c_function; if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) { js_debug dbg = {0}; js_debug_info(ctx, func_obj, &dbg); ctx->trace_hook(ctx, JS_HOOK_CALL, &dbg, ctx->trace_data); free_js_debug_info(ctx, &dbg); } switch(cproto) { case JS_CFUNC_constructor: case JS_CFUNC_constructor_or_func: /* Constructors are called as regular functions with JS_NULL this */ this_obj = JS_NULL; /* fall thru */ case JS_CFUNC_generic: ret_val = func.generic(ctx, this_obj, argc, arg_buf); break; case JS_CFUNC_constructor_magic: case JS_CFUNC_constructor_or_func_magic: /* Constructors are called as regular functions with JS_NULL this */ this_obj = JS_NULL; /* fall thru */ case JS_CFUNC_generic_magic: ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, p->u.cfunc.magic); break; case JS_CFUNC_getter: ret_val = func.getter(ctx, this_obj); break; case JS_CFUNC_setter: ret_val = func.setter(ctx, this_obj, arg_buf[0]); break; case JS_CFUNC_getter_magic: ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); break; case JS_CFUNC_setter_magic: ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); break; case JS_CFUNC_f_f: { double d1; if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { ret_val = JS_EXCEPTION; break; } ret_val = JS_NewFloat64(ctx, func.f_f(d1)); } break; case JS_CFUNC_f_f_f: { double d1, d2; if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { ret_val = JS_EXCEPTION; break; } if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { ret_val = JS_EXCEPTION; break; } ret_val = JS_NewFloat64(ctx, func.f_f_f(d1, d2)); } break; default: abort(); } rt->current_stack_frame = sf->prev_frame; if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) { js_debug dbg = {0}; js_debug_info(ctx, func_obj, &dbg); ctx->trace_hook(ctx, JS_HOOK_RET, &dbg, ctx->trace_data); free_js_debug_info(ctx, &dbg); } return ret_val; } static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSObject *p; JSBoundFunction *bf; JSValueConst *arg_buf, new_target; int arg_count, i; p = JS_VALUE_GET_OBJ(func_obj); bf = p->u.bound_function; arg_count = bf->argc + argc; if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) return JS_ThrowStackOverflow(ctx); arg_buf = alloca(sizeof(JSValue) * arg_count); for(i = 0; i < bf->argc; i++) { arg_buf[i] = bf->argv[i]; } for(i = 0; i < argc; i++) { arg_buf[bf->argc + i] = argv[i]; } return JS_Call(ctx, bf->func_obj, bf->this_val, arg_count, arg_buf); } /* argument of OP_special_object */ typedef enum { OP_SPECIAL_OBJECT_THIS_FUNC, OP_SPECIAL_OBJECT_NEW_TARGET, OP_SPECIAL_OBJECT_VAR_OBJECT, } OPSpecialObjectEnum; /* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ #ifdef DUMP_PROFILE /* Sampling profiler: try to take a sample */ static void profile_try_sample(JSRuntime *rt, JSFunctionBytecode *b, const uint8_t *pc) { struct timeval now; long elapsed_us; gettimeofday(&now, NULL); elapsed_us = (now.tv_sec - rt->profile.last_sample_time.tv_sec) * 1000000L + (now.tv_usec - rt->profile.last_sample_time.tv_usec); /* Only sample if enough time has elapsed */ if (elapsed_us >= rt->profile.sample_interval_us) { if (rt->profile.sample_count < rt->profile.sample_capacity) { JSProfileSample *sample = &rt->profile.samples[rt->profile.sample_count++]; sample->func = b; sample->pc_offset = (uint32_t)(pc - b->byte_code_buf); sample->callsite_id = 0; /* Could be enhanced to track callsite */ } rt->profile.last_sample_time = now; } } /* Helper function to record a call site */ static void profile_record_call_site(JSRuntime *rt, JSFunctionBytecode *b, uint32_t pc_offset) { uint32_t i; /* Check if we already have this callsite */ for (i = 0; i < b->call_site_count; i++) { if (b->call_sites[i].pc_offset == pc_offset) { b->call_sites[i].hit_count++; return; } } /* Add new callsite */ if (b->call_site_count >= b->call_site_capacity) { uint32_t new_cap = b->call_site_capacity ? b->call_site_capacity * 2 : 16; JSCallSite *new_sites = js_realloc_rt(rt, b->call_sites, new_cap * sizeof(JSCallSite)); if (!new_sites) return; b->call_sites = new_sites; b->call_site_capacity = new_cap; } b->call_sites[b->call_site_count].pc_offset = pc_offset; b->call_sites[b->call_site_count].hit_count = 1; b->call_site_count++; } /* Helper function to record a property access site */ static void profile_record_prop_site(JSRuntime *rt, JSFunctionBytecode *b, uint32_t pc_offset, JSAtom atom) { uint32_t i; /* Check if we already have this prop site */ for (i = 0; i < b->prop_site_count; i++) { if (b->prop_sites[i].pc_offset == pc_offset && b->prop_sites[i].atom == atom) { b->prop_sites[i].hit_count++; return; } } /* Add new prop site */ if (b->prop_site_count >= b->prop_site_capacity) { uint32_t new_cap = b->prop_site_capacity ? b->prop_site_capacity * 2 : 16; JSPropSite *new_sites = js_realloc_rt(rt, b->prop_sites, new_cap * sizeof(JSPropSite)); if (!new_sites) return; b->prop_sites = new_sites; b->prop_site_capacity = new_cap; } b->prop_sites[b->prop_site_count].pc_offset = pc_offset; b->prop_sites[b->prop_site_count].atom = atom; b->prop_sites[b->prop_site_count].hit_count = 1; b->prop_site_count++; } #endif /* VM frame management for trampoline - no malloc/free! */ static struct VMFrame *vm_push_frame(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags, const uint8_t *ret_pc, int ret_sp_offset, int call_argc, int call_has_this) { JSObject *p; JSFunctionBytecode *b; struct VMFrame *frame; int total_slots, i, arg_allocated_size; JSValue *stack_base, *arg_buf, *var_buf; p = JS_VALUE_GET_OBJ(func_obj); b = p->u.func.function_bytecode; /* Check frame stack capacity */ if (ctx->frame_stack_top + 1 >= ctx->frame_stack_capacity) { /* TODO: grow frame stack (for now, fail) */ return NULL; } /* Calculate space needed: args + vars + operand stack */ if (argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV)) { arg_allocated_size = b->arg_count; } else { arg_allocated_size = 0; } total_slots = arg_allocated_size + b->var_count + b->stack_size; /* Check value stack capacity */ if (ctx->value_stack_top + total_slots > ctx->value_stack_capacity) { /* TODO: grow value stack (for now, fail) */ return NULL; } /* Push frame */ ctx->frame_stack_top++; frame = &ctx->frame_stack[ctx->frame_stack_top]; /* Store offsets (safe with realloc) */ frame->value_stack_base = ctx->value_stack_top; frame->stack_size_allocated = total_slots; /* Get pointers for initialization (only used locally) */ stack_base = &ctx->value_stack[frame->value_stack_base]; if (arg_allocated_size) { frame->arg_buf_offset = 0; arg_buf = stack_base; for (i = 0; i < min_int(argc, b->arg_count); i++) arg_buf[i] = JS_DupValue(ctx, argv[i]); for (; i < b->arg_count; i++) arg_buf[i] = JS_NULL; frame->arg_count = b->arg_count; } else { frame->arg_buf_offset = -1; /* signal: args are aliased from caller */ frame->arg_count = argc; } frame->var_buf_offset = arg_allocated_size; var_buf = stack_base + arg_allocated_size; for (i = 0; i < b->var_count; i++) var_buf[i] = JS_NULL; frame->sp_offset = arg_allocated_size + b->var_count; /* Initialize frame metadata */ frame->cur_func = JS_DupValue(ctx, func_obj); frame->this_obj = JS_DupValue(ctx, this_obj); frame->new_target = JS_DupValue(ctx, new_target); frame->b = b; frame->ctx = b->realm; frame->pc = b->byte_code_buf; frame->js_mode = b->js_mode; frame->var_refs = p->u.func.var_refs; init_list_head(&frame->var_ref_list); /* Continuation info for return */ frame->ret_pc = ret_pc; frame->ret_sp_offset = ret_sp_offset; frame->call_argc = call_argc; frame->call_has_this = call_has_this; /* Bump value stack top */ ctx->value_stack_top += total_slots; return frame; } static void vm_pop_frame(JSContext *ctx) { struct VMFrame *frame; int i; struct list_head *el, *el1; JSValue *stack_base; if (ctx->frame_stack_top < 0) return; frame = &ctx->frame_stack[ctx->frame_stack_top]; stack_base = &ctx->value_stack[frame->value_stack_base]; /* Close variable references */ if (!list_empty(&frame->var_ref_list)) { list_for_each_safe(el, el1, &frame->var_ref_list) { struct JSVarRef *var_ref = list_entry(el, struct JSVarRef, var_ref_link); var_ref->value = JS_DupValue(ctx, *var_ref->pvalue); var_ref->pvalue = &var_ref->value; var_ref->is_detached = TRUE; list_del(&var_ref->var_ref_link); } } /* Free all values in this frame's value stack region */ for (i = 0; i < frame->stack_size_allocated; i++) JS_FreeValue(ctx, stack_base[i]); /* Free frame values */ JS_FreeValue(ctx, frame->cur_func); JS_FreeValue(ctx, frame->this_obj); JS_FreeValue(ctx, frame->new_target); /* Pop frame and value stack */ ctx->value_stack_top -= frame->stack_size_allocated; ctx->frame_stack_top--; } /* Helper: get pointer from offset (used in hot path) */ static inline JSValue *vm_frame_get_stack_ptr(JSContext *ctx, struct VMFrame *frame, int offset) { return &ctx->value_stack[frame->value_stack_base + offset]; } /* Helper: get current sp pointer for a frame */ static inline JSValue *vm_frame_get_sp(JSContext *ctx, struct VMFrame *frame) { return &ctx->value_stack[frame->value_stack_base + frame->sp_offset]; } /* Helper: get var_buf pointer for a frame */ static inline JSValue *vm_frame_get_var_buf(JSContext *ctx, struct VMFrame *frame) { return &ctx->value_stack[frame->value_stack_base + frame->var_buf_offset]; } /* Helper: get arg_buf pointer for a frame (or NULL if aliased) */ static inline JSValue *vm_frame_get_arg_buf(JSContext *ctx, struct VMFrame *frame) { if (frame->arg_buf_offset < 0) return NULL; /* aliased */ return &ctx->value_stack[frame->value_stack_base + frame->arg_buf_offset]; } /* Trampoline VM dispatcher - runs frames without C recursion */ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags) { JSRuntime *rt = caller_ctx->rt; JSObject *p; JSValue ret_val = JS_NULL; struct VMFrame *frame; /* Check if function is callable */ if (js_poll_interrupts(caller_ctx)) return JS_EXCEPTION; if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) return JS_ThrowTypeError(caller_ctx, "not a function"); p = JS_VALUE_GET_OBJ(func_obj); if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { JSClassCall *call_func; call_func = rt->class_array[p->class_id].call; if (!call_func) return JS_ThrowTypeError(caller_ctx, "not a function"); return call_func(caller_ctx, func_obj, this_obj, argc, (JSValueConst *)argv, flags); } /* Push initial frame (entry point, no continuation) */ frame = vm_push_frame(caller_ctx, func_obj, this_obj, new_target, argc, argv, flags, NULL, 0, 0, 0); if (!frame) return JS_ThrowStackOverflow(caller_ctx); /* Trampoline loop - execute frames without C recursion */ while (caller_ctx->frame_stack_top >= 0) { /* For now, fall back to old JS_CallInternal for the actual execution */ /* This is a stub - we'll implement vm_execute_frame next */ /* TODO: call vm_execute_frame(caller_ctx, frame) here */ /* For now, just return and clean up */ vm_pop_frame(caller_ctx); break; } return ret_val; } /* Execute a single frame (delegates to OLD implementation for now) */ static VMExecState vm_execute_frame(JSContext *ctx, struct VMFrame *frame, JSValue *ret_val, VMCallInfo *call_info) { /* TODO: Replace with proper bytecode loop extraction */ /* For now, delegate to the old recursive implementation */ *ret_val = JS_CallInternal_OLD(ctx, frame->cur_func, frame->this_obj, frame->new_target, frame->arg_count, vm_frame_get_arg_buf(ctx, frame), 0); if (JS_IsException(*ret_val)) return VM_EXEC_EXCEPTION; return VM_EXEC_RETURN; } /* Trampoline-based JS_CallInternal - eliminates C stack recursion */ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags) { /* TODO: Implement full trampoline */ /* For now, just delegate to OLD implementation */ return JS_CallInternal_OLD(caller_ctx, func_obj, this_obj, new_target, argc, argv, flags); } /* OLD recursive implementation - to be removed after trampoline is complete */ static JSValue JS_CallInternal_OLD(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags) { JSRuntime *rt = caller_ctx->rt; JSContext *ctx; JSObject *p; JSFunctionBytecode *b; JSStackFrame sf_s, *sf = &sf_s; const uint8_t *pc; int opcode, arg_allocated_size, i; JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; JSVarRef **var_refs; size_t alloca_size; #if !DIRECT_DISPATCH #define SWITCH(pc) switch (opcode = *pc++) #define CASE(op) case op #define DEFAULT default #define BREAK break #define SWITCH_OPCODE(new_op, target_label) do { \ const uint8_t *instr_ptr = pc-1; \ uint8_t *bc = (uint8_t *)b->byte_code_buf; \ size_t instr_idx = instr_ptr - b->byte_code_buf; \ bc[instr_idx] = new_op; \ goto target_label; \ } while(0) #else static const void * const dispatch_table[256] = { #define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, #if SHORT_OPCODES #define def(id, size, n_pop, n_push, f) #else #define def(id, size, n_pop, n_push, f) && case_default, #endif #include "quickjs-opcode.h" [ OP_COUNT ... 255 ] = &&case_default }; #define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; #define CASE(op) case_ ## op #define DEFAULT case_default #define BREAK SWITCH(pc) #endif if (js_poll_interrupts(caller_ctx)) return JS_EXCEPTION; if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { goto not_a_function; } p = JS_VALUE_GET_OBJ(func_obj); if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { JSClassCall *call_func; call_func = rt->class_array[p->class_id].call; if (!call_func) { not_a_function: return JS_ThrowTypeError(caller_ctx, "not a function"); } return call_func(caller_ctx, func_obj, this_obj, argc, (JSValueConst *)argv, flags); } b = p->u.func.function_bytecode; #ifdef DUMP_PROFILE /* Increment function entry count */ b->profile.entry_count++; #endif if (unlikely(caller_ctx->trace_hook) && (caller_ctx->trace_type & JS_HOOK_CALL)) { js_debug dbg = {0}; js_debug_info(caller_ctx, func_obj, &dbg); caller_ctx->trace_hook(caller_ctx, JS_HOOK_CALL, &dbg, caller_ctx->trace_data); free_js_debug_info(caller_ctx, &dbg); } if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { arg_allocated_size = b->arg_count; } else { arg_allocated_size = 0; } alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + b->stack_size); if (js_check_stack_overflow(rt, alloca_size)) return JS_ThrowStackOverflow(caller_ctx); sf->js_mode = b->js_mode; arg_buf = argv; sf->arg_count = argc; sf->cur_func = (JSValue)func_obj; init_list_head(&sf->var_ref_list); var_refs = p->u.func.var_refs; local_buf = alloca(alloca_size); if (unlikely(arg_allocated_size)) { int n = min_int(argc, b->arg_count); arg_buf = local_buf; for(i = 0; i < n; i++) arg_buf[i] = JS_DupValue(caller_ctx, argv[i]); for(; i < b->arg_count; i++) arg_buf[i] = JS_NULL; sf->arg_count = b->arg_count; } var_buf = local_buf + arg_allocated_size; sf->var_buf = var_buf; sf->arg_buf = arg_buf; for(i = 0; i < b->var_count; i++) var_buf[i] = JS_NULL; stack_buf = var_buf + b->var_count; sp = stack_buf; pc = b->byte_code_buf; sf->prev_frame = rt->current_stack_frame; rt->current_stack_frame = sf; ctx = b->realm; /* set the current realm */ restart: for(;;) { int call_argc; JSValue *call_argv; #ifdef DUMP_PROFILE /* Count bytecode instructions executed (self_ticks) */ b->profile.self_ticks++; /* Try to take a profiling sample */ profile_try_sample(rt, b, pc); #endif SWITCH(pc) { CASE(OP_push_i32): *sp++ = JS_NewInt32(ctx, get_u32(pc)); pc += 4; BREAK; CASE(OP_push_const): *sp++ = JS_DupValue(ctx, b->cpool[get_u32(pc)]); pc += 4; BREAK; #if SHORT_OPCODES CASE(OP_push_minus1): CASE(OP_push_0): CASE(OP_push_1): CASE(OP_push_2): CASE(OP_push_3): CASE(OP_push_4): CASE(OP_push_5): CASE(OP_push_6): CASE(OP_push_7): *sp++ = JS_NewInt32(ctx, opcode - OP_push_0); BREAK; CASE(OP_push_i8): *sp++ = JS_NewInt32(ctx, get_i8(pc)); pc += 1; BREAK; CASE(OP_push_i16): *sp++ = JS_NewInt32(ctx, get_i16(pc)); pc += 2; BREAK; CASE(OP_push_const8): *sp++ = JS_DupValue(ctx, b->cpool[*pc++]); BREAK; CASE(OP_fclosure8): *sp++ = js_closure(ctx, JS_DupValue(ctx, b->cpool[*pc++]), var_refs, sf); if (unlikely(JS_IsException(sp[-1]))) goto exception; BREAK; CASE(OP_push_empty_string): *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); BREAK; CASE(OP_get_length): { JSValue val; /* User-defined functions don't support property access in cell script */ if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(sp[-1]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { JS_ThrowTypeError(ctx, "cannot get property of function"); goto exception; } } sf->cur_pc = pc; val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); if (unlikely(JS_IsException(val))) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = val; } BREAK; #endif CASE(OP_push_atom_value): *sp++ = JS_AtomToValue(ctx, get_u32(pc)); pc += 4; BREAK; CASE(OP_null): *sp++ = JS_NULL; BREAK; CASE(OP_push_this): /* OP_push_this is only called at the start of a function */ { JSValue val = JS_DupValue(ctx, this_obj); *sp++ = val; } BREAK; CASE(OP_push_false): *sp++ = JS_FALSE; BREAK; CASE(OP_push_true): *sp++ = JS_TRUE; BREAK; CASE(OP_object): *sp++ = JS_NewObject(ctx); if (unlikely(JS_IsException(sp[-1]))) goto exception; BREAK; CASE(OP_special_object): { int arg = *pc++; switch(arg) { case OP_SPECIAL_OBJECT_THIS_FUNC: *sp++ = JS_DupValue(ctx, sf->cur_func); break; case OP_SPECIAL_OBJECT_NEW_TARGET: *sp++ = JS_DupValue(ctx, new_target); break; case OP_SPECIAL_OBJECT_VAR_OBJECT: *sp++ = JS_NewObjectProto(ctx, JS_NULL); if (unlikely(JS_IsException(sp[-1]))) goto exception; break; default: abort(); } } BREAK; CASE(OP_rest): { int first = get_u16(pc); pc += 2; *sp++ = js_build_rest(ctx, first, argc, (JSValueConst *)argv); if (unlikely(JS_IsException(sp[-1]))) goto exception; } BREAK; CASE(OP_drop): JS_FreeValue(ctx, sp[-1]); sp--; BREAK; CASE(OP_nip): JS_FreeValue(ctx, sp[-2]); sp[-2] = sp[-1]; sp--; BREAK; CASE(OP_nip1): /* a b c -> b c */ JS_FreeValue(ctx, sp[-3]); sp[-3] = sp[-2]; sp[-2] = sp[-1]; sp--; BREAK; CASE(OP_dup): sp[0] = JS_DupValue(ctx, sp[-1]); sp++; BREAK; CASE(OP_dup2): /* a b -> a b a b */ sp[0] = JS_DupValue(ctx, sp[-2]); sp[1] = JS_DupValue(ctx, sp[-1]); sp += 2; BREAK; CASE(OP_dup3): /* a b c -> a b c a b c */ sp[0] = JS_DupValue(ctx, sp[-3]); sp[1] = JS_DupValue(ctx, sp[-2]); sp[2] = JS_DupValue(ctx, sp[-1]); sp += 3; BREAK; CASE(OP_dup1): /* a b -> a a b */ sp[0] = sp[-1]; sp[-1] = JS_DupValue(ctx, sp[-2]); sp++; BREAK; CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ sp[0] = sp[-1]; sp[-1] = sp[-2]; sp[-2] = JS_DupValue(ctx, sp[0]); sp++; BREAK; CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ sp[0] = sp[-1]; sp[-1] = sp[-2]; sp[-2] = sp[-3]; sp[-3] = JS_DupValue(ctx, sp[0]); sp++; BREAK; CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ sp[0] = sp[-1]; sp[-1] = sp[-2]; sp[-2] = sp[-3]; sp[-3] = sp[-4]; sp[-4] = JS_DupValue(ctx, sp[0]); sp++; BREAK; CASE(OP_perm3): /* obj a b -> a obj b (213) */ { JSValue tmp; tmp = sp[-2]; sp[-2] = sp[-3]; sp[-3] = tmp; } BREAK; CASE(OP_rot3l): /* x a b -> a b x (231) */ { JSValue tmp; tmp = sp[-3]; sp[-3] = sp[-2]; sp[-2] = sp[-1]; sp[-1] = tmp; } BREAK; CASE(OP_rot4l): /* x a b c -> a b c x */ { JSValue tmp; tmp = sp[-4]; sp[-4] = sp[-3]; sp[-3] = sp[-2]; sp[-2] = sp[-1]; sp[-1] = tmp; } BREAK; CASE(OP_rot5l): /* x a b c d -> a b c d x */ { JSValue tmp; tmp = sp[-5]; sp[-5] = sp[-4]; sp[-4] = sp[-3]; sp[-3] = sp[-2]; sp[-2] = sp[-1]; sp[-1] = tmp; } BREAK; CASE(OP_rot3r): /* a b x -> x a b (312) */ { JSValue tmp; tmp = sp[-1]; sp[-1] = sp[-2]; sp[-2] = sp[-3]; sp[-3] = tmp; } BREAK; CASE(OP_perm4): /* obj prop a b -> a obj prop b */ { JSValue tmp; tmp = sp[-2]; sp[-2] = sp[-3]; sp[-3] = sp[-4]; sp[-4] = tmp; } BREAK; CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ { JSValue tmp; tmp = sp[-2]; sp[-2] = sp[-3]; sp[-3] = sp[-4]; sp[-4] = sp[-5]; sp[-5] = tmp; } BREAK; CASE(OP_swap): /* a b -> b a */ { JSValue tmp; tmp = sp[-2]; sp[-2] = sp[-1]; sp[-1] = tmp; } BREAK; CASE(OP_swap2): /* a b c d -> c d a b */ { JSValue tmp1, tmp2; tmp1 = sp[-4]; tmp2 = sp[-3]; sp[-4] = sp[-2]; sp[-3] = sp[-1]; sp[-2] = tmp1; sp[-1] = tmp2; } BREAK; CASE(OP_fclosure): { JSValue bfunc = JS_DupValue(ctx, b->cpool[get_u32(pc)]); pc += 4; *sp++ = js_closure(ctx, bfunc, var_refs, sf); if (unlikely(JS_IsException(sp[-1]))) goto exception; } BREAK; #if SHORT_OPCODES CASE(OP_call0): CASE(OP_call1): CASE(OP_call2): CASE(OP_call3): call_argc = opcode - OP_call0; goto has_call_argc; #endif CASE(OP_call): CASE(OP_tail_call): { call_argc = get_u16(pc); pc += 2; goto has_call_argc; has_call_argc: call_argv = sp - call_argc; sf->cur_pc = pc; #ifdef DUMP_PROFILE /* Record call site */ profile_record_call_site(rt, b, (uint32_t)(pc - b->byte_code_buf)); #endif /* TODO: Use trampoline - for now keep recursive */ ret_val = JS_CallInternal_OLD(ctx, call_argv[-1], JS_NULL, JS_NULL, call_argc, call_argv, 0); if (unlikely(JS_IsException(ret_val))) goto exception; if (opcode == OP_tail_call) goto done; for(i = -1; i < call_argc; i++) JS_FreeValue(ctx, call_argv[i]); sp -= call_argc + 1; *sp++ = ret_val; } BREAK; CASE(OP_call_method): CASE(OP_tail_call_method): { BOOL is_proxy = FALSE; call_argc = get_u16(pc); pc += 2; call_argv = sp - call_argc; sf->cur_pc = pc; #ifdef DUMP_PROFILE /* Record call site */ profile_record_call_site(rt, b, (uint32_t)(pc - b->byte_code_buf)); #endif /* Proxy method-call: detect [bytecode_func, "name", ...args] and rewrite as func("name", [args]) */ if (JS_VALUE_GET_TAG(call_argv[-2]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(call_argv[-2]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { if (!JS_IsFunction(ctx, call_argv[-1])) { int t = JS_VALUE_GET_TAG(call_argv[-1]); if (t == JS_TAG_STRING || t == JS_TAG_SYMBOL) is_proxy = TRUE; } } } if (is_proxy) { JSValue name = call_argv[-1]; JSValue args = JS_NewArray(ctx); if (unlikely(JS_IsException(args))) goto exception; /* Move args into the array, then null out stack slots. */ for(i = 0; i < call_argc; i++) { int r = JS_DefinePropertyValue(ctx, args, __JS_AtomFromUInt32(i), call_argv[i], JS_PROP_C_W_E | JS_PROP_THROW); call_argv[i] = JS_NULL; if (unlikely(r < 0)) { JS_FreeValue(ctx, args); goto exception; } } { JSValue proxy_argv[2]; proxy_argv[0] = name; /* still owned by stack; freed by normal cleanup */ proxy_argv[1] = args; ret_val = JS_CallInternal_OLD(ctx, call_argv[-2], JS_NULL, JS_NULL, 2, proxy_argv, 0); JS_FreeValue(ctx, args); } } else { ret_val = JS_CallInternal_OLD(ctx, call_argv[-1], call_argv[-2], JS_NULL, call_argc, call_argv, 0); } if (unlikely(JS_IsException(ret_val))) goto exception; if (opcode == OP_tail_call_method) goto done; for(i = -2; i < call_argc; i++) JS_FreeValue(ctx, call_argv[i]); sp -= call_argc + 2; *sp++ = ret_val; } BREAK; CASE(OP_array_from): { int i, ret; call_argc = get_u16(pc); pc += 2; ret_val = JS_NewArray(ctx); if (unlikely(JS_IsException(ret_val))) goto exception; call_argv = sp - call_argc; for(i = 0; i < call_argc; i++) { ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i], JS_PROP_C_W_E | JS_PROP_THROW); call_argv[i] = JS_NULL; if (ret < 0) { JS_FreeValue(ctx, ret_val); goto exception; } } sp -= call_argc; *sp++ = ret_val; } BREAK; CASE(OP_apply): { int magic; BOOL is_proxy = FALSE; magic = get_u16(pc); pc += 2; sf->cur_pc = pc; /* Proxy method-call with spread: detect ["name", bytecode_func, args_array] and rewrite as func("name", args_array) */ if (JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(sp[-2]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { if (!JS_IsFunction(ctx, sp[-3])) { int t = JS_VALUE_GET_TAG(sp[-3]); if (t == JS_TAG_STRING || t == JS_TAG_SYMBOL) is_proxy = TRUE; } } } if (is_proxy) { JSValue proxy_argv[2]; proxy_argv[0] = sp[-3]; /* name */ proxy_argv[1] = sp[-1]; /* args array already built by bytecode */ ret_val = JS_CallInternal_OLD(ctx, sp[-2], JS_NULL, JS_NULL, 2, proxy_argv, 0); } else { ret_val = js_function_apply(ctx, sp[-3], 2, (JSValueConst *)&sp[-2], magic); } if (unlikely(JS_IsException(ret_val))) goto exception; JS_FreeValue(ctx, sp[-3]); JS_FreeValue(ctx, sp[-2]); JS_FreeValue(ctx, sp[-1]); sp -= 3; *sp++ = ret_val; } BREAK; CASE(OP_return): ret_val = *--sp; goto done; CASE(OP_return_undef): ret_val = JS_NULL; goto done; CASE(OP_throw): JS_Throw(ctx, *--sp); goto exception; CASE(OP_throw_error): #define JS_THROW_VAR_RO 0 #define JS_THROW_VAR_REDECL 1 #define JS_THROW_VAR_UNINITIALIZED 2 #define JS_THROW_ERROR_ITERATOR_THROW 4 { JSAtom atom; int type; atom = get_u32(pc); type = pc[4]; pc += 5; if (type == JS_THROW_VAR_RO) JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); else if (type == JS_THROW_VAR_REDECL) JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); else if (type == JS_THROW_VAR_UNINITIALIZED) JS_ThrowReferenceErrorUninitialized(ctx, atom); else if (type == JS_THROW_ERROR_ITERATOR_THROW) JS_ThrowTypeError(ctx, "iterator does not have a throw method"); else JS_ThrowInternalError(ctx, "invalid throw var type %d", type); } goto exception; CASE(OP_eval): { JSValueConst obj; int scope_idx; call_argc = get_u16(pc); scope_idx = get_u16(pc + 2) + ARG_SCOPE_END; pc += 4; call_argv = sp - call_argc; sf->cur_pc = pc; if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { if (call_argc >= 1) obj = call_argv[0]; else obj = JS_NULL; ret_val = JS_EvalObject(ctx, JS_NULL, obj, JS_EVAL_TYPE_DIRECT, scope_idx); } else { ret_val = JS_CallInternal_OLD(ctx, call_argv[-1], JS_NULL, JS_NULL, call_argc, call_argv, 0); } if (unlikely(JS_IsException(ret_val))) goto exception; for(i = -1; i < call_argc; i++) JS_FreeValue(ctx, call_argv[i]); sp -= call_argc + 1; *sp++ = ret_val; } BREAK; /* could merge with OP_apply */ CASE(OP_apply_eval): { int scope_idx; uint32_t len; JSValue *tab; JSValueConst obj; scope_idx = get_u16(pc) + ARG_SCOPE_END; pc += 2; sf->cur_pc = pc; tab = build_arg_list(ctx, &len, sp[-1]); if (!tab) goto exception; if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { if (len >= 1) obj = tab[0]; else obj = JS_NULL; ret_val = JS_EvalObject(ctx, JS_NULL, obj, JS_EVAL_TYPE_DIRECT, scope_idx); } else { ret_val = JS_Call(ctx, sp[-2], JS_NULL, len, (JSValueConst *)tab); } free_arg_list(ctx, tab, len); if (unlikely(JS_IsException(ret_val))) goto exception; JS_FreeValue(ctx, sp[-2]); JS_FreeValue(ctx, sp[-1]); sp -= 2; *sp++ = ret_val; } BREAK; CASE(OP_regexp): { sp[-2] = js_regexp_constructor_internal(ctx, JS_NULL, sp[-2], sp[-1]); sp--; } BREAK; CASE(OP_check_var): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; ret = JS_CheckGlobalVar(ctx, atom); if (ret < 0) goto exception; *sp++ = JS_NewBool(ctx, ret); } BREAK; CASE(OP_get_var_undef): CASE(OP_get_var): { JSValue val; JSAtom atom; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); if (unlikely(JS_IsException(val))) goto exception; *sp++ = val; } BREAK; CASE(OP_put_var): CASE(OP_put_var_init): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); sp--; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_put_var_strict): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; /* sp[-2] is JS_TRUE or JS_FALSE */ if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) { JS_ThrowReferenceErrorNotDefined(ctx, atom); goto exception; } ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2); sp -= 2; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_check_define_var): { JSAtom atom; int flags; atom = get_u32(pc); flags = pc[4]; pc += 5; sf->cur_pc = pc; if (JS_CheckDefineGlobalVar(ctx, atom, flags)) goto exception; } BREAK; CASE(OP_define_var): { JSAtom atom; int flags; atom = get_u32(pc); flags = pc[4]; pc += 5; sf->cur_pc = pc; if (JS_DefineGlobalVar(ctx, atom, flags)) goto exception; } BREAK; CASE(OP_define_func): { JSAtom atom; int flags; atom = get_u32(pc); flags = pc[4]; pc += 5; sf->cur_pc = pc; if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) goto exception; JS_FreeValue(ctx, sp[-1]); sp--; } BREAK; CASE(OP_get_loc): { int idx; idx = get_u16(pc); pc += 2; sp[0] = JS_DupValue(ctx, var_buf[idx]); sp++; } BREAK; CASE(OP_put_loc): { int idx; idx = get_u16(pc); pc += 2; set_value(ctx, &var_buf[idx], sp[-1]); sp--; } BREAK; CASE(OP_set_loc): { int idx; idx = get_u16(pc); pc += 2; set_value(ctx, &var_buf[idx], JS_DupValue(ctx, sp[-1])); } BREAK; CASE(OP_get_arg): { int idx; idx = get_u16(pc); pc += 2; sp[0] = JS_DupValue(ctx, arg_buf[idx]); sp++; } BREAK; CASE(OP_put_arg): { int idx; idx = get_u16(pc); pc += 2; set_value(ctx, &arg_buf[idx], sp[-1]); sp--; } BREAK; CASE(OP_set_arg): { int idx; idx = get_u16(pc); pc += 2; set_value(ctx, &arg_buf[idx], JS_DupValue(ctx, sp[-1])); } BREAK; #if SHORT_OPCODES CASE(OP_get_loc8): *sp++ = JS_DupValue(ctx, var_buf[*pc++]); BREAK; CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_get_loc0): *sp++ = JS_DupValue(ctx, var_buf[0]); BREAK; CASE(OP_get_loc1): *sp++ = JS_DupValue(ctx, var_buf[1]); BREAK; CASE(OP_get_loc2): *sp++ = JS_DupValue(ctx, var_buf[2]); BREAK; CASE(OP_get_loc3): *sp++ = JS_DupValue(ctx, var_buf[3]); BREAK; CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; CASE(OP_set_loc0): set_value(ctx, &var_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_loc1): set_value(ctx, &var_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_loc2): set_value(ctx, &var_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_loc3): set_value(ctx, &var_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_get_arg0): *sp++ = JS_DupValue(ctx, arg_buf[0]); BREAK; CASE(OP_get_arg1): *sp++ = JS_DupValue(ctx, arg_buf[1]); BREAK; CASE(OP_get_arg2): *sp++ = JS_DupValue(ctx, arg_buf[2]); BREAK; CASE(OP_get_arg3): *sp++ = JS_DupValue(ctx, arg_buf[3]); BREAK; CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_get_var_ref0): *sp++ = JS_DupValue(ctx, *var_refs[0]->pvalue); BREAK; CASE(OP_get_var_ref1): *sp++ = JS_DupValue(ctx, *var_refs[1]->pvalue); BREAK; CASE(OP_get_var_ref2): *sp++ = JS_DupValue(ctx, *var_refs[2]->pvalue); BREAK; CASE(OP_get_var_ref3): *sp++ = JS_DupValue(ctx, *var_refs[3]->pvalue); BREAK; CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; #endif CASE(OP_get_var_ref): { int idx; JSValue val; idx = get_u16(pc); pc += 2; val = *var_refs[idx]->pvalue; sp[0] = JS_DupValue(ctx, val); sp++; } BREAK; CASE(OP_put_var_ref): { int idx; idx = get_u16(pc); pc += 2; set_value(ctx, var_refs[idx]->pvalue, sp[-1]); sp--; } BREAK; CASE(OP_set_var_ref): { int idx; idx = get_u16(pc); pc += 2; set_value(ctx, var_refs[idx]->pvalue, JS_DupValue(ctx, sp[-1])); } BREAK; CASE(OP_get_var_ref_check): { int idx; JSValue val; idx = get_u16(pc); pc += 2; val = *var_refs[idx]->pvalue; if (unlikely(JS_IsUninitialized(val))) { JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); goto exception; } sp[0] = JS_DupValue(ctx, val); sp++; } BREAK; CASE(OP_put_var_ref_check): { int idx; idx = get_u16(pc); pc += 2; if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); goto exception; } set_value(ctx, var_refs[idx]->pvalue, sp[-1]); sp--; } BREAK; CASE(OP_put_var_ref_check_init): { int idx; idx = get_u16(pc); pc += 2; if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); goto exception; } set_value(ctx, var_refs[idx]->pvalue, sp[-1]); sp--; } BREAK; CASE(OP_set_loc_uninitialized): { int idx; idx = get_u16(pc); pc += 2; set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); } BREAK; CASE(OP_get_loc_check): { int idx; idx = get_u16(pc); pc += 2; if (unlikely(JS_IsUninitialized(var_buf[idx]))) { JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); goto exception; } sp[0] = JS_DupValue(ctx, var_buf[idx]); sp++; } BREAK; CASE(OP_get_loc_checkthis): { int idx; idx = get_u16(pc); pc += 2; if (unlikely(JS_IsUninitialized(var_buf[idx]))) { JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, FALSE); goto exception; } sp[0] = JS_DupValue(ctx, var_buf[idx]); sp++; } BREAK; CASE(OP_put_loc_check): { int idx; idx = get_u16(pc); pc += 2; if (unlikely(JS_IsUninitialized(var_buf[idx]))) { JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); goto exception; } set_value(ctx, &var_buf[idx], sp[-1]); sp--; } BREAK; CASE(OP_put_loc_check_init): { int idx; idx = get_u16(pc); pc += 2; if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { JS_ThrowReferenceError(ctx, "'this' can be initialized only once"); goto exception; } set_value(ctx, &var_buf[idx], sp[-1]); sp--; } BREAK; CASE(OP_close_loc): { int idx; idx = get_u16(pc); pc += 2; close_lexical_var(ctx, sf, idx); } BREAK; CASE(OP_make_loc_ref): CASE(OP_make_arg_ref): CASE(OP_make_var_ref_ref): { JSVarRef *var_ref; JSProperty *pr; JSAtom atom; int idx; atom = get_u32(pc); idx = get_u16(pc + 4); pc += 6; *sp++ = JS_NewObjectProto(ctx, JS_NULL); if (unlikely(JS_IsException(sp[-1]))) goto exception; if (opcode == OP_make_var_ref_ref) { var_ref = var_refs[idx]; var_ref->header.ref_count++; } else { var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); if (!var_ref) goto exception; } pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, JS_PROP_WRITABLE | JS_PROP_VARREF); if (!pr) { free_var_ref(rt, var_ref); goto exception; } pr->u.var_ref = var_ref; *sp++ = JS_AtomToValue(ctx, atom); } BREAK; CASE(OP_make_var_ref): { JSAtom atom; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; if (JS_GetGlobalVarRef(ctx, atom, sp)) goto exception; sp += 2; } BREAK; CASE(OP_goto): pc += (int32_t)get_u32(pc); if (unlikely(js_poll_interrupts(ctx))) goto exception; BREAK; #if SHORT_OPCODES CASE(OP_goto16): pc += (int16_t)get_u16(pc); if (unlikely(js_poll_interrupts(ctx))) goto exception; BREAK; CASE(OP_goto8): pc += (int8_t)pc[0]; if (unlikely(js_poll_interrupts(ctx))) goto exception; BREAK; #endif CASE(OP_if_true): { int res; JSValue op1; op1 = sp[-1]; pc += 4; if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { res = JS_VALUE_GET_INT(op1); } else { res = JS_ToBoolFree(ctx, op1); } sp--; if (res) { pc += (int32_t)get_u32(pc - 4) - 4; } if (unlikely(js_poll_interrupts(ctx))) goto exception; } BREAK; CASE(OP_if_false): { int res; JSValue op1; op1 = sp[-1]; pc += 4; /* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and JS_TAG_UNDEFINED */ if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { res = JS_VALUE_GET_INT(op1); } else { res = JS_ToBoolFree(ctx, op1); } sp--; if (!res) { pc += (int32_t)get_u32(pc - 4) - 4; } if (unlikely(js_poll_interrupts(ctx))) goto exception; } BREAK; #if SHORT_OPCODES CASE(OP_if_true8): { int res; JSValue op1; op1 = sp[-1]; pc += 1; if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { res = JS_VALUE_GET_INT(op1); } else { res = JS_ToBoolFree(ctx, op1); } sp--; if (res) { pc += (int8_t)pc[-1] - 1; } if (unlikely(js_poll_interrupts(ctx))) goto exception; } BREAK; CASE(OP_if_false8): { int res; JSValue op1; op1 = sp[-1]; pc += 1; if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { res = JS_VALUE_GET_INT(op1); } else { res = JS_ToBoolFree(ctx, op1); } sp--; if (!res) { pc += (int8_t)pc[-1] - 1; } if (unlikely(js_poll_interrupts(ctx))) goto exception; } BREAK; #endif CASE(OP_catch): { int32_t diff; diff = get_u32(pc); sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); sp++; pc += 4; } BREAK; CASE(OP_gosub): { int32_t diff; diff = get_u32(pc); /* XXX: should have a different tag to avoid security flaw */ sp[0] = JS_NewInt32(ctx, pc + 4 - b->byte_code_buf); sp++; pc += diff; } BREAK; CASE(OP_ret): { JSValue op1; uint32_t pos; op1 = sp[-1]; if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) goto ret_fail; pos = JS_VALUE_GET_INT(op1); if (unlikely(pos >= b->byte_code_len)) { ret_fail: JS_ThrowInternalError(ctx, "invalid ret value"); goto exception; } sp--; pc = b->byte_code_buf + pos; } BREAK; CASE(OP_nip_catch): { JSValue ret_val; /* catch_offset ... ret_val -> ret_eval */ ret_val = *--sp; while (sp > stack_buf && JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { JS_FreeValue(ctx, *--sp); } if (unlikely(sp == stack_buf)) { JS_ThrowInternalError(ctx, "nip_catch"); JS_FreeValue(ctx, ret_val); goto exception; } sp[-1] = ret_val; } BREAK; CASE(OP_lnot): { int res; JSValue op1; op1 = sp[-1]; if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { res = JS_VALUE_GET_INT(op1) != 0; } else { res = JS_ToBoolFree(ctx, op1); } sp[-1] = JS_NewBool(ctx, !res); } BREAK; CASE(OP_get_field): { JSValue val; JSAtom atom; JSValue obj; const uint8_t *ic_pc; ic_pc = pc - 1; /* PC of opcode, before consuming operands */ atom = get_u32(pc); pc += 4; sf->cur_pc = pc; #ifdef DUMP_PROFILE /* Record property access site */ profile_record_prop_site(rt, b, (uint32_t)(pc - b->byte_code_buf), atom); #endif obj = sp[-1]; /* User-defined functions don't support property access in cell script */ if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(obj); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { JS_ThrowTypeError(ctx, "cannot get property of function"); goto exception; } } /* Monomorphic IC fast path: shape-guarded own-property lookup */ if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { JSObject *p = JS_VALUE_GET_OBJ(obj); JSShape *sh = p->shape; /* Simple thread-local IC cache using PC as key */ static __thread struct { const uint8_t *pc; JSShape *shape; uint32_t prop_idx; JSAtom atom; } ic_cache[256]; uint32_t cache_idx = ((uintptr_t)ic_pc >> 3) & 255; struct { const uint8_t *pc; JSShape *shape; uint32_t prop_idx; JSAtom atom; } *slot = &ic_cache[cache_idx]; /* IC hit: shape guard passed */ if (likely(slot->pc == ic_pc && slot->shape == sh && slot->atom == atom)) { JSProperty *pr = &p->prop[slot->prop_idx]; JSShapeProperty *prs = &get_shape_prop(sh)[slot->prop_idx]; /* Double-check it's still a normal data property */ if (likely((prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL)) { val = JS_DupValue(ctx, pr->u.value); JS_FreeValue(ctx, obj); sp[-1] = val; goto get_field_done; } } /* IC miss: do lookup and populate cache if it's an own data property */ { JSProperty *pr; JSShapeProperty *prs = find_own_property(&pr, p, atom); if (prs && (prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL) { /* Cache this for next time */ uint32_t prop_idx = prs - get_shape_prop(sh); slot->pc = ic_pc; slot->shape = sh; slot->prop_idx = prop_idx; slot->atom = atom; val = JS_DupValue(ctx, pr->u.value); JS_FreeValue(ctx, obj); sp[-1] = val; goto get_field_done; } } } /* Slow path: proto chain, getters, non-objects, etc. */ val = JS_GetProperty(ctx, obj, atom); if (unlikely(JS_IsException(val))) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = val; get_field_done: ; } BREAK; CASE(OP_get_field2): { JSValue val; JSAtom atom; JSValue obj; const uint8_t *ic_pc; ic_pc = pc - 1; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; #ifdef DUMP_PROFILE /* Record property access site */ profile_record_prop_site(rt, b, (uint32_t)(pc - b->byte_code_buf), atom); #endif obj = sp[-1]; /* Proxy method-call sugar: func.name(...) -> func("name", [args...]) OP_get_field2 is only emitted when a call immediately follows. */ if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(obj); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { val = JS_AtomToValue(ctx, atom); /* "name" */ if (unlikely(JS_IsException(val))) goto exception; *sp++ = val; /* stack becomes [func, "name"] */ goto get_field2_done; } } /* Monomorphic IC fast path */ if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { JSObject *p = JS_VALUE_GET_OBJ(obj); JSShape *sh = p->shape; static __thread struct { const uint8_t *pc; JSShape *shape; uint32_t prop_idx; JSAtom atom; } ic_cache2[256]; uint32_t cache_idx = ((uintptr_t)ic_pc >> 3) & 255; struct { const uint8_t *pc; JSShape *shape; uint32_t prop_idx; JSAtom atom; } *slot = &ic_cache2[cache_idx]; if (likely(slot->pc == ic_pc && slot->shape == sh && slot->atom == atom)) { JSProperty *pr = &p->prop[slot->prop_idx]; JSShapeProperty *prs = &get_shape_prop(sh)[slot->prop_idx]; if (likely((prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL)) { val = JS_DupValue(ctx, pr->u.value); *sp++ = val; goto get_field2_done; } } { JSProperty *pr; JSShapeProperty *prs = find_own_property(&pr, p, atom); if (prs && (prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL) { uint32_t prop_idx = prs - get_shape_prop(sh); slot->pc = ic_pc; slot->shape = sh; slot->prop_idx = prop_idx; slot->atom = atom; val = JS_DupValue(ctx, pr->u.value); *sp++ = val; goto get_field2_done; } } } /* Slow path */ val = JS_GetProperty(ctx, obj, atom); if (unlikely(JS_IsException(val))) goto exception; *sp++ = val; get_field2_done: ; } BREAK; CASE(OP_put_field): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; #ifdef DUMP_PROFILE /* Record property access site */ profile_record_prop_site(rt, b, (uint32_t)(pc - b->byte_code_buf), atom); #endif /* User-defined functions don't support property assignment in cell script */ if (JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(sp[-2]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { JS_ThrowTypeError(ctx, "cannot set property of function"); goto exception; } } ret = JS_SetPropertyInternal(ctx, sp[-2], atom, sp[-1], sp[-2], JS_PROP_THROW_STRICT); JS_FreeValue(ctx, sp[-2]); sp -= 2; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_define_field): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], JS_PROP_C_W_E | JS_PROP_THROW); sp--; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_set_name): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_set_name_computed): { int ret; ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_set_proto): { JSValue proto; sf->cur_pc = pc; proto = sp[-1]; if (JS_IsObject(proto) || JS_IsNull(proto)) { if (JS_SetPrototypeInternal(ctx, sp[-2], proto) < 0) goto exception; } JS_FreeValue(ctx, proto); sp--; } BREAK; CASE(OP_define_method): CASE(OP_define_method_computed): { JSValue getter, setter, value; JSValueConst obj; JSAtom atom; int flags, ret, op_flags; BOOL is_computed; #define OP_DEFINE_METHOD_METHOD 0 #define OP_DEFINE_METHOD_GETTER 1 #define OP_DEFINE_METHOD_SETTER 2 #define OP_DEFINE_METHOD_ENUMERABLE 4 is_computed = (opcode == OP_define_method_computed); if (is_computed) { atom = JS_ValueToAtom(ctx, sp[-2]); if (unlikely(atom == JS_ATOM_NULL)) goto exception; opcode += OP_define_method - OP_define_method_computed; } else { atom = get_u32(pc); pc += 4; } op_flags = *pc++; obj = sp[-2 - is_computed]; flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) flags |= JS_PROP_ENUMERABLE; op_flags &= 3; value = JS_NULL; getter = JS_NULL; setter = JS_NULL; if (op_flags == OP_DEFINE_METHOD_METHOD) { value = sp[-1]; flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; } else if (op_flags == OP_DEFINE_METHOD_GETTER) { getter = sp[-1]; flags |= JS_PROP_HAS_GET; } else { setter = sp[-1]; flags |= JS_PROP_HAS_SET; } ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); if (ret >= 0) { ret = JS_DefineProperty(ctx, obj, atom, value, getter, setter, flags); } JS_FreeValue(ctx, sp[-1]); if (is_computed) { JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, sp[-2]); } sp -= 1 + is_computed; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_define_class): CASE(OP_define_class_computed): JS_ThrowTypeError(ctx, "classes are not supported"); goto exception; CASE(OP_get_array_el): { JSValue val; /* User-defined functions don't support property access in cell script */ if (JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(sp[-2]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { JS_ThrowTypeError(ctx, "cannot get property of function"); goto exception; } } sf->cur_pc = pc; val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); JS_FreeValue(ctx, sp[-2]); sp[-2] = val; sp--; if (unlikely(JS_IsException(val))) goto exception; } BREAK; CASE(OP_get_array_el2): { JSValue val; /* Proxy method-call sugar for bracket calls: func[key](...) */ if (JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(sp[-2]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { /* Keep [func, key] and normalize key to property-key (string/symbol). */ switch (JS_VALUE_GET_TAG(sp[-1])) { case JS_TAG_INT: /* Convert integer to string */ sf->cur_pc = pc; ret_val = JS_ToString(ctx, sp[-1]); if (JS_IsException(ret_val)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret_val; break; case JS_TAG_STRING: case JS_TAG_SYMBOL: break; default: sf->cur_pc = pc; ret_val = JS_ToPropertyKey(ctx, sp[-1]); if (JS_IsException(ret_val)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret_val; break; } BREAK; /* skip JS_GetPropertyValue, keep [func, key] on stack */ } } sf->cur_pc = pc; val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); sp[-1] = val; if (unlikely(JS_IsException(val))) goto exception; } BREAK; CASE(OP_get_array_el3): { JSValue val; /* User-defined functions don't support property access in cell script */ if (JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(sp[-2]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { JS_ThrowTypeError(ctx, "cannot get property of function"); goto exception; } } switch (JS_VALUE_GET_TAG(sp[-2])) { case JS_TAG_INT: case JS_TAG_STRING: case JS_TAG_SYMBOL: /* undefined and null are tested in JS_GetPropertyValue() */ break; default: /* must be tested nefore JS_ToPropertyKey */ if (unlikely(JS_IsNull(sp[-2]) || JS_IsNull(sp[-2]))) { JS_ThrowTypeError(ctx, "value has no property"); goto exception; } sf->cur_pc = pc; ret_val = JS_ToPropertyKey(ctx, sp[-1]); if (JS_IsException(ret_val)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret_val; break; } sf->cur_pc = pc; val = JS_GetPropertyValue(ctx, sp[-2], JS_DupValue(ctx, sp[-1])); *sp++ = val; if (unlikely(JS_IsException(val))) goto exception; } BREAK; CASE(OP_get_ref_value): { JSValue val; JSAtom atom; int ret; sf->cur_pc = pc; atom = JS_ValueToAtom(ctx, sp[-1]); if (atom == JS_ATOM_NULL) goto exception; if (unlikely(JS_IsNull(sp[-2]))) { JS_ThrowReferenceErrorNotDefined(ctx, atom); JS_FreeAtom(ctx, atom); goto exception; } ret = JS_HasProperty(ctx, sp[-2], atom); if (unlikely(ret <= 0)) { if (ret < 0) { JS_FreeAtom(ctx, atom); goto exception; } JS_ThrowReferenceErrorNotDefined(ctx, atom); JS_FreeAtom(ctx, atom); goto exception; val = JS_NULL; } else { val = JS_GetProperty(ctx, sp[-2], atom); } JS_FreeAtom(ctx, atom); if (unlikely(JS_IsException(val))) goto exception; sp[0] = val; sp++; } BREAK; CASE(OP_put_array_el): { int ret; /* User-defined functions don't support property assignment in cell script */ if (JS_VALUE_GET_TAG(sp[-3]) == JS_TAG_OBJECT) { JSObject *fp = JS_VALUE_GET_OBJ(sp[-3]); if (fp->class_id == JS_CLASS_BYTECODE_FUNCTION) { JS_ThrowTypeError(ctx, "cannot set property of function"); goto exception; } } sf->cur_pc = pc; ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); JS_FreeValue(ctx, sp[-3]); sp -= 3; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_put_ref_value): { int ret; JSAtom atom; sf->cur_pc = pc; atom = JS_ValueToAtom(ctx, sp[-2]); if (unlikely(atom == JS_ATOM_NULL)) goto exception; if (unlikely(JS_IsNull(sp[-3]))) { JS_ThrowReferenceErrorNotDefined(ctx, atom); JS_FreeAtom(ctx, atom); goto exception; } ret = JS_HasProperty(ctx, sp[-3], atom); if (unlikely(ret <= 0)) { if (unlikely(ret < 0)) { JS_FreeAtom(ctx, atom); goto exception; } JS_ThrowReferenceErrorNotDefined(ctx, atom); JS_FreeAtom(ctx, atom); goto exception; } ret = JS_SetPropertyInternal(ctx, sp[-3], atom, sp[-1], sp[-3], JS_PROP_THROW_STRICT); JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, sp[-2]); JS_FreeValue(ctx, sp[-3]); sp -= 3; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_define_array_el): { int ret; ret = JS_DefinePropertyValueValue(ctx, sp[-3], JS_DupValue(ctx, sp[-2]), sp[-1], JS_PROP_C_W_E | JS_PROP_THROW); sp -= 1; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_copy_data_properties): /* target source excludeList */ { /* stack offsets (-1 based): 2 bits for target, 3 bits for source, 2 bits for exclusionList */ int mask; mask = *pc++; sf->cur_pc = pc; if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], sp[-1 - ((mask >> 2) & 7)], sp[-1 - ((mask >> 5) & 7)], 0)) goto exception; } BREAK; CASE(OP_add): CASE(OP_add_float): { JSValue op1 = sp[-2], op2 = sp[-1], res; int tag1 = JS_VALUE_GET_NORM_TAG(op1); int tag2 = JS_VALUE_GET_NORM_TAG(op2); /* 1) both ints? keep fast int path with overflow check */ if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { int64_t tmp = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); if (likely((int)tmp == tmp)) { res = JS_NewInt32(ctx, (int)tmp); } else { res = __JS_NewFloat64(ctx, (double)JS_VALUE_GET_INT(op1) + (double)JS_VALUE_GET_INT(op2)); } } /* 2) both floats? */ else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { res = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + JS_VALUE_GET_FLOAT64(op2)); } /* 3) both strings? */ else if (JS_IsString(op1) && JS_IsString(op2)) { res = JS_ConcatString(ctx, op1, op2); if (JS_IsException(res)) goto exception; } /* 4) mixed int/float? promote to float */ // TODO: Seems slow else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { double a, b; if(tag1 == JS_TAG_INT) a = (double)JS_VALUE_GET_INT(op1); else a = JS_VALUE_GET_FLOAT64(op1); if(tag2 == JS_TAG_INT) b = (double)JS_VALUE_GET_INT(op2); else b = JS_VALUE_GET_FLOAT64(op2); res = __JS_NewFloat64(ctx, a + b); } else if (tag1 == JS_TAG_NULL || tag2 == JS_TAG_NULL) { res = JS_NULL; } /* 5) anything else → throw */ else { JS_ThrowTypeError(ctx, "cannot concatenate with string"); goto exception; } sp[-2] = res; sp--; } BREAK; CASE(OP_add_loc): { int idx = *pc++; JSValue rhs = sp[-1]; JSValue lhs = var_buf[idx]; JSValue res; int tag1 = JS_VALUE_GET_NORM_TAG(lhs); int tag2 = JS_VALUE_GET_NORM_TAG(rhs); /* 1) both ints? fast path, overflow → float64 */ if (likely(JS_VALUE_IS_BOTH_INT(lhs, rhs))) { int a_i = JS_VALUE_GET_INT(lhs); int b_i = JS_VALUE_GET_INT(rhs); int64_t tmp = (int64_t)a_i + b_i; if ((int)tmp == tmp) res = JS_NewInt32(ctx, (int)tmp); else res = __JS_NewFloat64(ctx, (double)a_i + (double)b_i); } /* 2) both floats? */ else if (JS_VALUE_IS_BOTH_FLOAT(lhs, rhs)) { res = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(lhs) + JS_VALUE_GET_FLOAT64(rhs)); } /* 3) both strings? */ else if (JS_IsString(lhs) && JS_IsString(rhs)) { res = JS_ConcatString(ctx, lhs, rhs); if (JS_IsException(res)) goto exception; } /* 4) mixed int/float? promote to float64 */ else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { double a = tag1 == JS_TAG_INT ? (double)JS_VALUE_GET_INT(lhs) : JS_VALUE_GET_FLOAT64(lhs); double b = tag2 == JS_TAG_INT ? (double)JS_VALUE_GET_INT(rhs) : JS_VALUE_GET_FLOAT64(rhs); res = __JS_NewFloat64(ctx, a + b); } /* 5) anything else → throw */ else { JS_ThrowTypeError(ctx, "cannot concatenate with string"); goto exception; } var_buf[idx] = res; sp--; } BREAK; CASE(OP_sub): CASE(OP_sub_float): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { int64_t r; r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); if (unlikely((int)r != r)) goto binary_arith_slow; sp[-2] = JS_NewInt32(ctx, r); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) - JS_VALUE_GET_FLOAT64(op2)); sp--; } else { goto binary_arith_slow; } } BREAK; CASE(OP_mul): CASE(OP_mul_float): { JSValue op1, op2; double d; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { int32_t v1, v2; int64_t r; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); r = (int64_t)v1 * v2; if (unlikely((int)r != r)) { d = (double)r; goto mul_fp_res; } /* need to test zero case for -0 result */ if (unlikely(r == 0 && (v1 | v2) < 0)) { d = -0.0; goto mul_fp_res; } sp[-2] = JS_NewInt32(ctx, r); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); mul_fp_res: sp[-2] = __JS_NewFloat64(ctx, d); sp--; } else { goto binary_arith_slow; } } BREAK; CASE(OP_div): CASE(OP_div_float): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { int v1, v2; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2); sp--; } else { goto binary_arith_slow; } } BREAK; CASE(OP_mod): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { int v1, v2, r; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = -1 and the cases where the result is -0. */ if (unlikely(v1 < 0 || v2 <= 0)) goto binary_arith_slow; r = v1 % v2; sp[-2] = JS_NewInt32(ctx, r); sp--; } else { goto binary_arith_slow; } } BREAK; CASE(OP_pow): binary_arith_slow: sf->cur_pc = pc; if (js_binary_arith_slow(ctx, sp, opcode)) goto exception; sp--; BREAK; CASE(OP_plus): { JSValue op1; uint32_t tag; op1 = sp[-1]; tag = JS_VALUE_GET_TAG(op1); if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { } else { sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } } BREAK; CASE(OP_neg): { JSValue op1; uint32_t tag; int val; double d; op1 = sp[-1]; tag = JS_VALUE_GET_TAG(op1); if (tag == JS_TAG_INT) { val = JS_VALUE_GET_INT(op1); /* Note: -0 cannot be expressed as integer */ if (unlikely(val == 0)) { d = -0.0; goto neg_fp_res; } if (unlikely(val == INT32_MIN)) { d = -(double)val; goto neg_fp_res; } sp[-1] = JS_NewInt32(ctx, -val); } else if (JS_TAG_IS_FLOAT64(tag)) { d = -JS_VALUE_GET_FLOAT64(op1); neg_fp_res: sp[-1] = __JS_NewFloat64(ctx, d); } else { sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } } BREAK; CASE(OP_inc): { JSValue op1; int val; op1 = sp[-1]; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { val = JS_VALUE_GET_INT(op1); if (unlikely(val == INT32_MAX)) goto inc_slow; sp[-1] = JS_NewInt32(ctx, val + 1); } else { inc_slow: sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } } BREAK; CASE(OP_dec): { JSValue op1; int val; op1 = sp[-1]; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { val = JS_VALUE_GET_INT(op1); if (unlikely(val == INT32_MIN)) goto dec_slow; sp[-1] = JS_NewInt32(ctx, val - 1); } else { dec_slow: sf->cur_pc = pc; if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } } BREAK; CASE(OP_post_inc): CASE(OP_post_dec): sf->cur_pc = pc; if (js_post_inc_slow(ctx, sp, opcode)) goto exception; sp++; BREAK; CASE(OP_inc_loc): { JSValue op1; int val; int idx; idx = *pc; pc += 1; op1 = var_buf[idx]; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { val = JS_VALUE_GET_INT(op1); if (unlikely(val == INT32_MAX)) goto inc_loc_slow; var_buf[idx] = JS_NewInt32(ctx, val + 1); } else { inc_loc_slow: sf->cur_pc = pc; /* must duplicate otherwise the variable value may be destroyed before JS code accesses it */ op1 = JS_DupValue(ctx, op1); if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) goto exception; set_value(ctx, &var_buf[idx], op1); } } BREAK; CASE(OP_dec_loc): { JSValue op1; int val; int idx; idx = *pc; pc += 1; op1 = var_buf[idx]; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { val = JS_VALUE_GET_INT(op1); if (unlikely(val == INT32_MIN)) goto dec_loc_slow; var_buf[idx] = JS_NewInt32(ctx, val - 1); } else { dec_loc_slow: sf->cur_pc = pc; /* must duplicate otherwise the variable value may be destroyed before JS code accesses it */ op1 = JS_DupValue(ctx, op1); if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) goto exception; set_value(ctx, &var_buf[idx], op1); } } BREAK; CASE(OP_not): { JSValue op1; op1 = sp[-1]; if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { sp[-1] = JS_NewInt32(ctx, ~JS_VALUE_GET_INT(op1)); } else { sf->cur_pc = pc; if (js_not_slow(ctx, sp)) goto exception; } } BREAK; CASE(OP_shl): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { uint32_t v1, v2; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); v2 &= 0x1f; sp[-2] = JS_NewInt32(ctx, v1 << v2); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { uint32_t v1, v2; v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); v2 &= 0x1f; sp[-2] = JS_NewInt32(ctx, v1 << v2); sp--; } else { sp[-2] = JS_NULL; sp--; } } BREAK; CASE(OP_shr): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { uint32_t v2; v2 = JS_VALUE_GET_INT(op2); v2 &= 0x1f; sp[-2] = JS_NewUint32(ctx, (uint32_t)JS_VALUE_GET_INT(op1) >> v2); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { uint32_t v1, v2; v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); v2 &= 0x1f; sp[-2] = JS_NewUint32(ctx, v1 >> v2); sp--; } else { sp[-2] = JS_NULL; sp--; } } BREAK; CASE(OP_sar): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { uint32_t v2; v2 = JS_VALUE_GET_INT(op2); v2 &= 0x1f; sp[-2] = JS_NewInt32(ctx, (int)JS_VALUE_GET_INT(op1) >> v2); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { int32_t v1; uint32_t v2; v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); v2 &= 0x1f; sp[-2] = JS_NewInt32(ctx, v1 >> v2); sp--; } else { sp[-2] = JS_NULL; sp--; } } BREAK; CASE(OP_and): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { sp[-2] = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1) & JS_VALUE_GET_INT(op2)); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { int32_t v1, v2; v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); sp[-2] = JS_NewInt32(ctx, v1 & v2); sp--; } else { sp[-2] = JS_NULL; sp--; } } BREAK; CASE(OP_or): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { sp[-2] = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1) | JS_VALUE_GET_INT(op2)); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { int32_t v1, v2; v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); sp[-2] = JS_NewInt32(ctx, v1 | v2); sp--; } else { sp[-2] = JS_NULL; sp--; } } BREAK; CASE(OP_xor): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { sp[-2] = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1) ^ JS_VALUE_GET_INT(op2)); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { int32_t v1, v2; v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); sp[-2] = JS_NewInt32(ctx, v1 ^ v2); sp--; } else { sp[-2] = JS_NULL; sp--; } } BREAK; #define OP_CMP(opcode, binary_op, slow_call) \ CASE(opcode): \ { \ JSValue op1, op2; \ op1 = sp[-2]; \ op2 = sp[-1]; \ if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ sp[-2] = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ sp--; \ } else { \ sf->cur_pc = pc; \ if (slow_call) \ goto exception; \ sp--; \ } \ } \ BREAK OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); CASE(OP_in): sf->cur_pc = pc; if (js_operator_in(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_delete): sf->cur_pc = pc; if (js_operator_delete(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_delete_var): { JSAtom atom; int ret; atom = get_u32(pc); pc += 4; sf->cur_pc = pc; ret = JS_DeleteGlobalVar(ctx, atom); if (unlikely(ret < 0)) goto exception; *sp++ = JS_NewBool(ctx, ret); } BREAK; CASE(OP_to_object): if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { sf->cur_pc = pc; ret_val = JS_ToObject(ctx, sp[-1]); if (JS_IsException(ret_val)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret_val; } BREAK; CASE(OP_to_propkey): switch (JS_VALUE_GET_TAG(sp[-1])) { case JS_TAG_INT: case JS_TAG_STRING: case JS_TAG_SYMBOL: break; default: sf->cur_pc = pc; ret_val = JS_ToPropertyKey(ctx, sp[-1]); if (JS_IsException(ret_val)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret_val; break; } BREAK; CASE(OP_template_concat): { int n, i; JSValue out; StringBuffer b_s, *b = &b_s; n = get_u16(pc); pc += 2; if (n <= 0) { *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); BREAK; } if (string_buffer_init(ctx, b, 64)) goto exception; for (i = 0; i < n; i++) { JSValue v = sp[i - n]; JSValue s = js_cell_text(ctx, JS_NULL, 1, &v); if (JS_IsException(s)) { string_buffer_free(b); goto exception; } if (string_buffer_concat_value_free(b, s)) { string_buffer_free(b); goto exception; } } out = string_buffer_end(b); if (JS_IsException(out)) goto exception; for (i = 0; i < n; i++) JS_FreeValue(ctx, sp[i - n]); sp -= n; *sp++ = out; } BREAK; CASE(OP_nop): BREAK; CASE(OP_is_null): if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { goto set_true; } else { goto free_and_set_false; } set_true: sp[-1] = JS_TRUE; BREAK; free_and_set_false: JS_FreeValue(ctx, sp[-1]); sp[-1] = JS_FALSE; BREAK; CASE(OP_invalid): DEFAULT: JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", (int)(pc - b->byte_code_buf - 1), opcode); goto exception; } } exception: if (is_backtrace_needed(ctx, rt->current_exception)) { /* add the backtrace information now (it is not done before if the exception happens in a bytecode operation */ sf->cur_pc = pc; build_backtrace(ctx, rt->current_exception, NULL, 0, 0, 0); } if (!rt->current_exception_is_uncatchable) { while (sp > stack_buf) { JSValue val = *--sp; JS_FreeValue(ctx, val); if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { int pos = JS_VALUE_GET_INT(val); if (pos != 0) { *sp++ = rt->current_exception; rt->current_exception = JS_UNINITIALIZED; pc = b->byte_code_buf + pos; goto restart; } } } } ret_val = JS_EXCEPTION; done: if (unlikely(!list_empty(&sf->var_ref_list))) { /* variable references reference the stack: must close them */ close_var_refs(rt, sf); } /* free the local variables and stack */ for(pval = local_buf; pval < sp; pval++) { JS_FreeValue(ctx, *pval); } rt->current_stack_frame = sf->prev_frame; if (unlikely(caller_ctx->trace_hook) && (caller_ctx->trace_type & JS_HOOK_RET)) { js_debug dbg = {0}; js_debug_info(caller_ctx, func_obj, &dbg); caller_ctx->trace_hook(caller_ctx, JS_HOOK_RET, &dbg, caller_ctx->trace_data); free_js_debug_info(caller_ctx, &dbg); } return ret_val; } JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv) { return JS_CallInternal(ctx, func_obj, this_obj, JS_NULL, argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); } static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, int argc, JSValueConst *argv) { JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_NULL, argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); JS_FreeValue(ctx, func_obj); return res; } /* warning: the refcount of the context is not incremented. Return NULL in case of exception (case of revoked proxy only) */ static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValueConst func_obj) { JSObject *p; JSContext *realm; if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) return ctx; p = JS_VALUE_GET_OBJ(func_obj); switch(p->class_id) { case JS_CLASS_C_FUNCTION: realm = p->u.cfunc.realm; break; case JS_CLASS_BYTECODE_FUNCTION: { JSFunctionBytecode *b; b = p->u.func.function_bytecode; realm = b->realm; } break; case JS_CLASS_BOUND_FUNCTION: { JSBoundFunction *bf = p->u.bound_function; realm = JS_GetFunctionRealm(ctx, bf->func_obj); } break; default: realm = ctx; break; } return realm; } static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor, int class_id) { /* Simplified: always use the default class prototype */ JSValue proto = JS_DupValue(ctx, ctx->class_proto[class_id]); JSValue obj = JS_NewObjectProtoClass(ctx, proto, class_id); JS_FreeValue(ctx, proto); return obj; } JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom, int argc, JSValueConst *argv) { JSValue func_obj; func_obj = JS_GetProperty(ctx, this_val, atom); if (JS_IsException(func_obj)) return func_obj; return JS_CallFree(ctx, func_obj, this_val, argc, argv); } static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, int argc, JSValueConst *argv) { JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); JS_FreeValue(ctx, this_val); return res; } /* JS parser */ enum { TOK_NUMBER = -128, TOK_STRING, TOK_TEMPLATE, TOK_IDENT, TOK_REGEXP, /* warning: order matters (see js_parse_assign_expr) */ TOK_MUL_ASSIGN, TOK_DIV_ASSIGN, TOK_MOD_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, TOK_SHL_ASSIGN, TOK_SAR_ASSIGN, TOK_SHR_ASSIGN, TOK_AND_ASSIGN, TOK_XOR_ASSIGN, TOK_OR_ASSIGN, TOK_POW_ASSIGN, TOK_LAND_ASSIGN, TOK_LOR_ASSIGN, TOK_DOUBLE_QUESTION_MARK_ASSIGN, TOK_DEC, TOK_INC, TOK_SHL, TOK_SAR, TOK_SHR, TOK_LT, TOK_LTE, TOK_GT, TOK_GTE, TOK_EQ, TOK_STRICT_EQ, TOK_NEQ, TOK_STRICT_NEQ, TOK_LAND, TOK_LOR, TOK_POW, TOK_ARROW, TOK_ELLIPSIS, TOK_DOUBLE_QUESTION_MARK, TOK_QUESTION_MARK_DOT, TOK_ERROR, TOK_PRIVATE_NAME, TOK_EOF, /* keywords: WARNING: same order as atoms */ TOK_NULL, /* must be first */ TOK_FALSE, TOK_TRUE, TOK_IF, TOK_ELSE, TOK_RETURN, TOK_GO, TOK_VAR, TOK_DEF, TOK_THIS, TOK_DELETE, TOK_VOID, TOK_NEW, TOK_IN, TOK_DO, TOK_WHILE, TOK_FOR, TOK_BREAK, TOK_CONTINUE, TOK_SWITCH, TOK_CASE, TOK_DEFAULT, TOK_THROW, TOK_TRY, TOK_CATCH, TOK_FINALLY, TOK_FUNCTION, TOK_DEBUGGER, TOK_WITH, /* FutureReservedWord */ TOK_CLASS, TOK_CONST, TOK_ENUM, TOK_EXPORT, TOK_EXTENDS, TOK_IMPORT, TOK_SUPER, /* FutureReservedWords when parsing strict mode code */ TOK_IMPLEMENTS, TOK_INTERFACE, TOK_LET, TOK_PRIVATE, TOK_PROTECTED, TOK_PUBLIC, TOK_STATIC, TOK_YIELD, TOK_AWAIT, /* must be last */ TOK_OF, /* only used for js_parse_skip_parens_token() */ }; #define TOK_FIRST_KEYWORD TOK_NULL #define TOK_LAST_KEYWORD TOK_AWAIT /* unicode code points */ #define CP_NBSP 0x00a0 #define CP_BOM 0xfeff #define CP_LS 0x2028 #define CP_PS 0x2029 typedef struct BlockEnv { struct BlockEnv *prev; JSAtom label_name; /* JS_ATOM_NULL if none */ int label_break; /* -1 if none */ int label_cont; /* -1 if none */ int drop_count; /* number of stack elements to drop */ int label_finally; /* -1 if none */ int scope_level; uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */ } BlockEnv; typedef struct JSGlobalVar { int cpool_idx; /* if >= 0, index in the constant pool for hoisted function defintion*/ uint8_t force_init : 1; /* force initialization to undefined */ uint8_t is_lexical : 1; /* global let/const definition */ uint8_t is_const : 1; /* const definition */ int scope_level; /* scope of definition */ JSAtom var_name; /* variable name */ } JSGlobalVar; typedef struct RelocEntry { struct RelocEntry *next; uint32_t addr; /* address to patch */ int size; /* address size: 1, 2 or 4 bytes */ } RelocEntry; typedef struct JumpSlot { int op; int size; int pos; int label; } JumpSlot; typedef struct LabelSlot { int ref_count; int pos; /* phase 1 address, -1 means not resolved yet */ int pos2; /* phase 2 address, -1 means not resolved yet */ int addr; /* phase 3 address, -1 means not resolved yet */ RelocEntry *first_reloc; } LabelSlot; typedef struct LineNumberSlot { uint32_t pc; uint32_t source_pos; } LineNumberSlot; typedef struct { /* last source position */ const uint8_t *ptr; int line_num; int col_num; const uint8_t *buf_start; } GetLineColCache; typedef enum JSParseFunctionEnum { JS_PARSE_FUNC_STATEMENT, JS_PARSE_FUNC_VAR, JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW, JS_PARSE_FUNC_GETTER, JS_PARSE_FUNC_SETTER, JS_PARSE_FUNC_METHOD, JS_PARSE_FUNC_CLASS_STATIC_INIT, JS_PARSE_FUNC_CLASS_CONSTRUCTOR, JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, } JSParseFunctionEnum; typedef struct JSFunctionDef { JSContext *ctx; struct JSFunctionDef *parent; int parent_cpool_idx; /* index in the constant pool of the parent or -1 if none */ int parent_scope_level; /* scope level in parent at point of definition */ struct list_head child_list; /* list of JSFunctionDef.link */ struct list_head link; BOOL is_eval; /* TRUE if eval code */ 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 */ BOOL has_prototype; /* true if a prototype field is necessary */ BOOL has_simple_parameter_list; BOOL has_parameter_expressions; /* if true, an argument scope is created */ 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_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 is_derived_class_constructor; BOOL in_function_body; 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 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 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 */ const uint8_t *last_opcode_source_ptr; 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 */ BOOL strip_debug : 1; /* strip all debug info (implies strip_source = TRUE) */ BOOL strip_source : 1; /* strip only source code */ JSAtom filename; uint32_t source_pos; /* pointer in the eval() source */ GetLineColCache *get_line_col_cache; /* XXX: could remove to save memory */ DynBuf pc2line; char *source; /* raw source, utf-8 encoded */ int source_len; } JSFunctionDef; typedef struct JSToken { int val; const uint8_t *ptr; /* position in the source */ union { struct { JSValue str; int sep; } str; struct { JSValue val; } num; struct { JSAtom atom; BOOL has_escape; BOOL is_reserved; } ident; struct { JSValue body; JSValue flags; } regexp; } u; } JSToken; typedef struct JSParseState { JSContext *ctx; const char *filename; JSToken token; BOOL got_lf; /* true if got line feed before the current token */ const uint8_t *last_ptr; const uint8_t *buf_start; const uint8_t *buf_ptr; const uint8_t *buf_end; /* current function code */ JSFunctionDef *cur_func; BOOL ext_json; /* true if accepting JSON superset */ GetLineColCache get_line_col_cache; } JSParseState; typedef struct JSOpCode { #ifdef DUMP_BYTECODE const char *name; #endif uint8_t size; /* in bytes */ /* the opcodes remove n_pop items from the top of the stack, then pushes n_push items */ uint8_t n_pop; uint8_t n_push; uint8_t fmt; } JSOpCode; static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { #define FMT(f) #ifdef DUMP_BYTECODE #define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, #else #define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, #endif #include "quickjs-opcode.h" #undef DEF #undef FMT }; #if SHORT_OPCODES /* After the final compilation pass, short opcodes are used. Their opcodes overlap with the temporary opcodes which cannot appear in the final bytecode. Their description is after the temporary opcodes in opcode_info[]. */ #define short_opcode_info(op) \ opcode_info[(op) >= OP_TEMP_START ? \ (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] #else #define short_opcode_info(op) opcode_info[op] #endif static __exception int next_token(JSParseState *s); static void free_token(JSParseState *s, JSToken *token) { switch(token->val) { case TOK_NUMBER: JS_FreeValue(s->ctx, token->u.num.val); break; case TOK_STRING: case TOK_TEMPLATE: JS_FreeValue(s->ctx, token->u.str.str); break; case TOK_REGEXP: JS_FreeValue(s->ctx, token->u.regexp.body); JS_FreeValue(s->ctx, token->u.regexp.flags); break; case TOK_IDENT: JS_FreeAtom(s->ctx, token->u.ident.atom); break; default: if (token->val >= TOK_FIRST_KEYWORD && token->val <= TOK_LAST_KEYWORD) { JS_FreeAtom(s->ctx, token->u.ident.atom); } break; } } static void __attribute((unused)) dump_token(JSParseState *s, const JSToken *token) { switch(token->val) { case TOK_NUMBER: { double d; JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ printf("number: %.14g\n", d); } break; case TOK_IDENT: dump_atom: { char buf[ATOM_GET_STR_BUF_SIZE]; printf("ident: '%s'\n", JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); } break; case TOK_STRING: { const char *str; /* XXX: quote the string */ str = JS_ToCString(s->ctx, token->u.str.str); printf("string: '%s'\n", str); JS_FreeCString(s->ctx, str); } break; case TOK_TEMPLATE: { const char *str; str = JS_ToCString(s->ctx, token->u.str.str); printf("template: `%s`\n", str); JS_FreeCString(s->ctx, str); } break; case TOK_REGEXP: { const char *str, *str2; str = JS_ToCString(s->ctx, token->u.regexp.body); str2 = JS_ToCString(s->ctx, token->u.regexp.flags); printf("regexp: '%s' '%s'\n", str, str2); JS_FreeCString(s->ctx, str); JS_FreeCString(s->ctx, str2); } break; case TOK_EOF: printf("eof\n"); break; default: if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { goto dump_atom; } else if (s->token.val >= 256) { printf("token: %d\n", token->val); } else { printf("token: '%c'\n", token->val); } break; } } /* return the zero based line and column number in the source. */ /* Note: we no longer support '\r' as line terminator */ static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len) { int line_num, col_num, c; size_t i; line_num = 0; col_num = 0; for(i = 0; i < len; i++) { c = buf[i]; if (c == '\n') { line_num++; col_num = 0; } else if (c < 0x80 || c >= 0xc0) { col_num++; } } *pcol_num = col_num; return line_num; } static int get_line_col_cached(GetLineColCache *s, int *pcol_num, const uint8_t *ptr) { int line_num, col_num; if (ptr >= s->ptr) { line_num = get_line_col(&col_num, s->ptr, ptr - s->ptr); if (line_num == 0) { s->col_num += col_num; } else { s->line_num += line_num; s->col_num = col_num; } } else { line_num = get_line_col(&col_num, ptr, s->ptr - ptr); if (line_num == 0) { s->col_num -= col_num; } else { const uint8_t *p; s->line_num -= line_num; /* find the absolute column position */ col_num = 0; for(p = ptr - 1; p >= s->buf_start; p--) { if (*p == '\n') { break; } else if (*p < 0x80 || *p >= 0xc0) { col_num++; } } s->col_num = col_num; } } s->ptr = ptr; *pcol_num = s->col_num; return s->line_num; } /* 'ptr' is the position of the error in the source */ static int js_parse_error_v(JSParseState *s, const uint8_t *ptr, const char *fmt, va_list ap) { JSContext *ctx = s->ctx; int line_num, col_num; line_num = get_line_col(&col_num, s->buf_start, ptr - s->buf_start); JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); build_backtrace(ctx, ctx->rt->current_exception, s->filename, line_num + 1, col_num + 1, 0); return -1; } static __attribute__((format(printf, 3, 4))) int js_parse_error_pos(JSParseState *s, const uint8_t *ptr, const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); ret = js_parse_error_v(s, ptr, fmt, ap); va_end(ap); return ret; } static __attribute__((format(printf, 2, 3))) int js_parse_error(JSParseState *s, const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); ret = js_parse_error_v(s, s->token.ptr, fmt, ap); va_end(ap); return ret; } static int js_parse_expect(JSParseState *s, int tok) { if (s->token.val != tok) { /* XXX: dump token correctly in all cases */ return js_parse_error(s, "expecting '%c'", tok); } return next_token(s); } static int js_parse_expect_semi(JSParseState *s) { if (s->token.val != ';') { /* automatic insertion of ';' */ if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { return 0; } return js_parse_error(s, "expecting '%c'", ';'); } return next_token(s); } static int js_parse_error_reserved_identifier(JSParseState *s) { char buf1[ATOM_GET_STR_BUF_SIZE]; return js_parse_error(s, "'%s' is a reserved identifier", JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), s->token.u.ident.atom)); } static __exception int js_parse_template_part(JSParseState *s, const uint8_t *p) { uint32_t c; StringBuffer b_s, *b = &b_s; JSValue str; /* p points to the first byte of the template part */ if (string_buffer_init(s->ctx, b, 32)) goto fail; for(;;) { if (p >= s->buf_end) goto unexpected_eof; c = *p++; if (c == '`') { /* template end part */ break; } if (c == '$' && *p == '{') { /* template start or middle part */ p++; break; } if (c == '\\') { if (string_buffer_putc8(b, c)) goto fail; if (p >= s->buf_end) goto unexpected_eof; c = *p++; } /* newline sequences are normalized as single '\n' bytes */ if (c == '\r') { if (*p == '\n') p++; c = '\n'; } if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); goto fail; } p = p_next; } if (string_buffer_putc(b, c)) goto fail; } str = string_buffer_end(b); if (JS_IsException(str)) return -1; s->token.val = TOK_TEMPLATE; s->token.u.str.sep = c; s->token.u.str.str = str; s->buf_ptr = p; return 0; unexpected_eof: js_parse_error(s, "unexpected end of string"); fail: string_buffer_free(b); return -1; } static __exception int js_parse_string(JSParseState *s, int sep, BOOL do_throw, const uint8_t *p, JSToken *token, const uint8_t **pp) { int ret; uint32_t c; StringBuffer b_s, *b = &b_s; const uint8_t *p_escape; JSValue str; /* string */ if (string_buffer_init(s->ctx, b, 32)) goto fail; for(;;) { if (p >= s->buf_end) goto invalid_char; c = *p; if (c < 0x20) { if (sep == '`') { if (c == '\r') { if (p[1] == '\n') p++; c = '\n'; } /* do not update s->line_num */ } else if (c == '\n' || c == '\r') goto invalid_char; } p++; if (c == sep) break; if (c == '$' && *p == '{' && sep == '`') { /* template start or middle part */ p++; break; } if (c == '\\') { p_escape = p - 1; c = *p; /* XXX: need a specific JSON case to avoid accepting invalid escapes */ switch(c) { case '\0': if (p >= s->buf_end) goto invalid_char; p++; break; case '\'': case '\"': case '\\': p++; break; case '\r': /* accept DOS and MAC newline sequences */ if (p[1] == '\n') { p++; } /* fall thru */ case '\n': /* ignore escaped newline sequence */ p++; continue; default: if (c >= '0' && c <= '9') { if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { p++; c = '\0'; } else { if (c >= '8' || sep == '`') { /* Note: according to ES2021, \8 and \9 are not accepted in strict mode or in templates. */ goto invalid_escape; } else { if (do_throw) js_parse_error_pos(s, p_escape, "octal escape sequences are not allowed in strict mode"); } goto fail; } } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { goto invalid_utf8; } p = p_next; /* LS or PS are skipped */ if (c == CP_LS || c == CP_PS) continue; } else { parse_escape: ret = lre_parse_escape(&p, TRUE); if (ret == -1) { invalid_escape: if (do_throw) js_parse_error_pos(s, p_escape, "malformed escape sequence in string literal"); goto fail; } else if (ret < 0) { /* ignore the '\' (could output a warning) */ p++; } else { c = ret; } } break; } } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) goto invalid_utf8; p = p_next; } if (string_buffer_putc(b, c)) goto fail; } str = string_buffer_end(b); if (JS_IsException(str)) return -1; token->val = TOK_STRING; token->u.str.sep = c; token->u.str.str = str; *pp = p; return 0; invalid_utf8: if (do_throw) js_parse_error(s, "invalid UTF-8 sequence"); goto fail; invalid_char: if (do_throw) js_parse_error(s, "unexpected end of string"); fail: string_buffer_free(b); return -1; } static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && !s->token.u.ident.has_escape; } static __exception int js_parse_regexp(JSParseState *s) { const uint8_t *p; BOOL in_class; StringBuffer b_s, *b = &b_s; StringBuffer b2_s, *b2 = &b2_s; uint32_t c; JSValue body_str, flags_str; p = s->buf_ptr; p++; in_class = FALSE; if (string_buffer_init(s->ctx, b, 32)) return -1; if (string_buffer_init(s->ctx, b2, 1)) goto fail; for(;;) { if (p >= s->buf_end) { eof_error: js_parse_error(s, "unexpected end of regexp"); goto fail; } c = *p++; if (c == '\n' || c == '\r') { goto eol_error; } else if (c == '/') { if (!in_class) break; } else if (c == '[') { in_class = TRUE; } else if (c == ']') { /* XXX: incorrect as the first character in a class */ in_class = FALSE; } else if (c == '\\') { if (string_buffer_putc8(b, c)) goto fail; c = *p++; if (c == '\n' || c == '\r') goto eol_error; else if (c == '\0' && p >= s->buf_end) goto eof_error; else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { goto invalid_utf8; } p = p_next; if (c == CP_LS || c == CP_PS) goto eol_error; } } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { invalid_utf8: js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); goto fail; } /* LS or PS are considered as line terminator */ if (c == CP_LS || c == CP_PS) { eol_error: js_parse_error_pos(s, p - 1, "unexpected line terminator in regexp"); goto fail; } p = p_next; } if (string_buffer_putc(b, c)) goto fail; } /* flags */ for(;;) { const uint8_t *p_next = p; c = *p_next++; if (c >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { p++; goto invalid_utf8; } } if (!lre_js_is_ident_next(c)) break; if (string_buffer_putc(b2, c)) goto fail; p = p_next; } body_str = string_buffer_end(b); flags_str = string_buffer_end(b2); if (JS_IsException(body_str) || JS_IsException(flags_str)) { JS_FreeValue(s->ctx, body_str); JS_FreeValue(s->ctx, flags_str); return -1; } s->token.val = TOK_REGEXP; s->token.u.regexp.body = body_str; s->token.u.regexp.flags = flags_str; s->buf_ptr = p; return 0; fail: string_buffer_free(b); string_buffer_free(b2); return -1; } static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, char *static_buf) { char *buf, *new_buf; size_t size, new_size; buf = *pbuf; size = *psize; if (size >= (SIZE_MAX / 3) * 2) new_size = SIZE_MAX; else new_size = size + (size >> 1); if (buf == static_buf) { new_buf = js_malloc(ctx, new_size); if (!new_buf) return -1; memcpy(new_buf, buf, size); } else { new_buf = js_realloc(ctx, buf, new_size); if (!new_buf) return -1; } *pbuf = new_buf; *psize = new_size; return 0; } /* convert a TOK_IDENT to a keyword when needed */ static void update_token_ident(JSParseState *s) { JSAtom atom = s->token.u.ident.atom; /* if it’s a (strict-mode) keyword, convert it */ if(atom <= JS_ATOM_LAST_STRICT_KEYWORD) { if(s->token.u.ident.has_escape) { /* identifiers with escape sequences stay identifiers */ s->token.u.ident.is_reserved = TRUE; s->token.val = TOK_IDENT; } else { /* keyword atoms are laid out so: TOK_FIRST_KEYWORD + (atom - 1) == the right token code */ s->token.val = TOK_FIRST_KEYWORD + (atom - 1); } } } /* if the current token is an identifier or keyword, reparse it according to the current function type */ static void reparse_ident_token(JSParseState *s) { if (s->token.val == TOK_IDENT || (s->token.val >= TOK_FIRST_KEYWORD && s->token.val <= TOK_LAST_KEYWORD)) { s->token.val = TOK_IDENT; s->token.u.ident.is_reserved = FALSE; update_token_ident(s); } } /* 'c' is the first character. Return JS_ATOM_NULL in case of error */ static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, BOOL *pident_has_escape, int c) { const uint8_t *p, *p1; char ident_buf[128], *buf; size_t ident_size, ident_pos; JSAtom atom; p = *pp; buf = ident_buf; ident_size = sizeof(ident_buf); ident_pos = 0; for(;;) { p1 = p; if (c < 128) { buf[ident_pos++] = c; } else { ident_pos += unicode_to_utf8((uint8_t*)buf + ident_pos, c); } c = *p1++; if (c == '\\' && *p1 == 'u') { c = lre_parse_escape(&p1, TRUE); *pident_has_escape = TRUE; } else if (c >= 128) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); } if (!lre_js_is_ident_next(c)) break; p = p1; if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) { atom = JS_ATOM_NULL; goto done; } } } atom = JS_NewAtomLen(s->ctx, buf, ident_pos); done: if (unlikely(buf != ident_buf)) js_free(s->ctx, buf); *pp = p; return atom; } static __exception int next_token(JSParseState *s) { const uint8_t *p; int c; BOOL ident_has_escape; JSAtom atom; if (js_check_stack_overflow(s->ctx->rt, 0)) { return js_parse_error(s, "stack overflow"); } free_token(s, &s->token); p = s->last_ptr = s->buf_ptr; s->got_lf = FALSE; redo: s->token.ptr = p; c = *p; switch(c) { case 0: if (p >= s->buf_end) { s->token.val = TOK_EOF; } else { goto def_token; } break; case '`': if (js_parse_template_part(s, p + 1)) goto fail; p = s->buf_ptr; break; case '\'': case '\"': if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p)) goto fail; break; case '\r': /* accept DOS and MAC newline sequences */ if (p[1] == '\n') { p++; } /* fall thru */ case '\n': p++; line_terminator: s->got_lf = TRUE; goto redo; case '\f': case '\v': case ' ': case '\t': p++; goto redo; case '/': if (p[1] == '*') { /* comment */ p += 2; for(;;) { if (*p == '\0' && p >= s->buf_end) { js_parse_error(s, "unexpected end of comment"); goto fail; } if (p[0] == '*' && p[1] == '/') { p += 2; break; } if (*p == '\n' || *p == '\r') { s->got_lf = TRUE; /* considered as LF for ASI */ p++; } else if (*p >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); if (c == CP_LS || c == CP_PS) { s->got_lf = TRUE; /* considered as LF for ASI */ } else if (c == -1) { p++; /* skip invalid UTF-8 */ } } else { p++; } } goto redo; } else if (p[1] == '/') { /* line comment */ p += 2; skip_line_comment: for(;;) { if (*p == '\0' && p >= s->buf_end) break; if (*p == '\r' || *p == '\n') break; if (*p >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); /* LS or PS are considered as line terminator */ if (c == CP_LS || c == CP_PS) { break; } else if (c == -1) { p++; /* skip invalid UTF-8 */ } } else { p++; } } goto redo; } else if (p[1] == '=') { p += 2; s->token.val = TOK_DIV_ASSIGN; } else { p++; s->token.val = c; } break; case '\\': if (p[1] == 'u') { const uint8_t *p1 = p + 1; int c1 = lre_parse_escape(&p1, TRUE); if (c1 >= 0 && lre_js_is_ident_first(c1)) { c = c1; p = p1; ident_has_escape = TRUE; goto has_ident; } else { /* XXX: syntax error? */ } } goto def_token; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '$': /* identifier */ p++; ident_has_escape = FALSE; has_ident: atom = parse_ident(s, &p, &ident_has_escape, c); if (atom == JS_ATOM_NULL) goto fail; s->token.u.ident.atom = atom; s->token.u.ident.has_escape = ident_has_escape; s->token.u.ident.is_reserved = FALSE; s->token.val = TOK_IDENT; update_token_ident(s); break; case '.': if (p[1] == '.' && p[2] == '.') { p += 3; s->token.val = TOK_ELLIPSIS; break; } if (p[1] >= '0' && p[1] <= '9') { goto parse_number; } else { goto def_token; } break; case '0': /* in strict mode, octal literals are not accepted */ if (is_digit(p[1])) { js_parse_error(s, "octal literals are not allowed"); goto fail; } goto parse_number; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* number */ parse_number: { JSValue ret; const uint8_t *p1; int flags; flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; ret = js_atof(s->ctx, (const char *)p, (const char **)&p, 0, flags); if (JS_IsException(ret)) goto fail; /* reject number immediately followed by identifier */ if (JS_VALUE_IS_NAN(ret) || lre_js_is_ident_next(unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1))) { JS_FreeValue(s->ctx, ret); js_parse_error(s, "invalid number literal"); goto fail; } s->token.val = TOK_NUMBER; s->token.u.num.val = ret; } break; case '*': if (p[1] == '=') { p += 2; s->token.val = TOK_MUL_ASSIGN; } else if (p[1] == '*') { if (p[2] == '=') { p += 3; s->token.val = TOK_POW_ASSIGN; } else { p += 2; s->token.val = TOK_POW; } } else { goto def_token; } break; case '%': if (p[1] == '=') { p += 2; s->token.val = TOK_MOD_ASSIGN; } else { goto def_token; } break; case '+': if (p[1] == '=') { p += 2; s->token.val = TOK_PLUS_ASSIGN; } else if (p[1] == '+') { p += 2; s->token.val = TOK_INC; } else { goto def_token; } break; case '-': if (p[1] == '=') { p += 2; s->token.val = TOK_MINUS_ASSIGN; } else if (p[1] == '-') { p += 2; s->token.val = TOK_DEC; } else { goto def_token; } break; case '<': if (p[1] == '=') { p += 2; s->token.val = TOK_LTE; } else if (p[1] == '<') { if (p[2] == '=') { p += 3; s->token.val = TOK_SHL_ASSIGN; } else { p += 2; s->token.val = TOK_SHL; } } else { goto def_token; } break; case '>': if (p[1] == '=') { p += 2; s->token.val = TOK_GTE; } else if (p[1] == '>') { if (p[2] == '>') { if (p[3] == '=') { p += 4; s->token.val = TOK_SHR_ASSIGN; } else { p += 3; s->token.val = TOK_SHR; } } else if (p[2] == '=') { p += 3; s->token.val = TOK_SAR_ASSIGN; } else { p += 2; s->token.val = TOK_SAR; } } else { goto def_token; } break; case '=': if (p[1] == '=') { p += 2; s->token.val = TOK_STRICT_EQ; } else if (p[1] == '>') { p += 2; s->token.val = TOK_ARROW; } else { goto def_token; } break; case '!': if (p[1] == '=') { p += 2; s->token.val = TOK_STRICT_NEQ; } else { goto def_token; } break; case '&': if (p[1] == '=') { p += 2; s->token.val = TOK_AND_ASSIGN; } else if (p[1] == '&') { if (p[2] == '=') { p += 3; s->token.val = TOK_LAND_ASSIGN; } else { p += 2; s->token.val = TOK_LAND; } } else { goto def_token; } break; case '^': if (p[1] == '=') { p += 2; s->token.val = TOK_XOR_ASSIGN; } else { goto def_token; } break; case '|': if (p[1] == '=') { p += 2; s->token.val = TOK_OR_ASSIGN; } else if (p[1] == '|') { if (p[2] == '=') { p += 3; s->token.val = TOK_LOR_ASSIGN; } else { p += 2; s->token.val = TOK_LOR; } } else { goto def_token; } break; case '?': if (p[1] == '?') { if (p[2] == '=') { p += 3; s->token.val = TOK_DOUBLE_QUESTION_MARK_ASSIGN; } else { p += 2; s->token.val = TOK_DOUBLE_QUESTION_MARK; } } else if (p[1] == '.' && !(p[2] >= '0' && p[2] <= '9')) { p += 2; s->token.val = TOK_QUESTION_MARK_DOT; } else { goto def_token; } break; default: if (c >= 128) { /* unicode value */ c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); switch(c) { case CP_PS: case CP_LS: /* XXX: should avoid incrementing line_number, but needed to handle HTML comments */ goto line_terminator; default: if (lre_is_space(c)) { goto redo; } else if (lre_js_is_ident_first(c)) { ident_has_escape = FALSE; goto has_ident; } else { js_parse_error(s, "unexpected character"); goto fail; } } } def_token: s->token.val = c; p++; break; } s->buf_ptr = p; // dump_token(s, &s->token); return 0; fail: s->token.val = TOK_ERROR; return -1; } /* 'c' is the first character. Return JS_ATOM_NULL in case of error */ /* XXX: accept unicode identifiers as JSON5 ? */ static JSAtom json_parse_ident(JSParseState *s, const uint8_t **pp, int c) { const uint8_t *p; char ident_buf[128], *buf; size_t ident_size, ident_pos; JSAtom atom; p = *pp; buf = ident_buf; ident_size = sizeof(ident_buf); ident_pos = 0; for(;;) { buf[ident_pos++] = c; c = *p; if (c >= 128 || !lre_is_id_continue_byte(c)) break; p++; if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) { atom = JS_ATOM_NULL; goto done; } } } atom = JS_NewAtomLen(s->ctx, buf, ident_pos); done: if (unlikely(buf != ident_buf)) js_free(s->ctx, buf); *pp = p; return atom; } static int json_parse_string(JSParseState *s, const uint8_t **pp, int sep) { const uint8_t *p, *p_next; int i; uint32_t c; StringBuffer b_s, *b = &b_s; if (string_buffer_init(s->ctx, b, 32)) goto fail; p = *pp; for(;;) { if (p >= s->buf_end) { goto end_of_input; } c = *p++; if (c == sep) break; if (c < 0x20) { js_parse_error_pos(s, p - 1, "Bad control character in string literal"); goto fail; } if (c == '\\') { c = *p++; switch(c) { case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case '\\': break; case '/': break; case 'u': c = 0; for(i = 0; i < 4; i++) { int h = from_hex(*p++); if (h < 0) { js_parse_error_pos(s, p - 1, "Bad Unicode escape"); goto fail; } c = (c << 4) | h; } break; case '\n': if (s->ext_json) continue; goto bad_escape; case 'v': if (s->ext_json) { c = '\v'; break; } goto bad_escape; default: if (c == sep) break; if (p > s->buf_end) goto end_of_input; bad_escape: js_parse_error_pos(s, p - 1, "Bad escaped character"); goto fail; } } else if (c >= 0x80) { c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { js_parse_error_pos(s, p - 1, "Bad UTF-8 sequence"); goto fail; } p = p_next; } if (string_buffer_putc(b, c)) goto fail; } s->token.val = TOK_STRING; s->token.u.str.sep = sep; s->token.u.str.str = string_buffer_end(b); *pp = p; return 0; end_of_input: js_parse_error(s, "Unexpected end of JSON input"); fail: string_buffer_free(b); return -1; } static int json_parse_number(JSParseState *s, const uint8_t **pp) { const uint8_t *p = *pp; const uint8_t *p_start = p; int radix; double d; JSATODTempMem atod_mem; if (*p == '+' || *p == '-') p++; if (!is_digit(*p)) { if (s->ext_json) { if (strstart((const char *)p, "Infinity", (const char **)&p)) { d = 1.0 / 0.0; if (*p_start == '-') d = -d; goto done; } else if (strstart((const char *)p, "NaN", (const char **)&p)) { d = NAN; goto done; } else if (*p != '.') { goto unexpected_token; } } else { goto unexpected_token; } } if (p[0] == '0') { if (s->ext_json) { /* also accepts base 16, 8 and 2 prefix for integers */ radix = 10; if (p[1] == 'x' || p[1] == 'X') { p += 2; radix = 16; } else if ((p[1] == 'o' || p[1] == 'O')) { p += 2; radix = 8; } else if ((p[1] == 'b' || p[1] == 'B')) { p += 2; radix = 2; } if (radix != 10) { /* prefix is present */ if (to_digit(*p) >= radix) { unexpected_token: return js_parse_error_pos(s, p, "Unexpected token '%c'", *p); } d = js_atod((const char *)p_start, (const char **)&p, 0, JS_ATOD_INT_ONLY | JS_ATOD_ACCEPT_BIN_OCT, &atod_mem); goto done; } } if (is_digit(p[1])) return js_parse_error_pos(s, p, "Unexpected number"); } while (is_digit(*p)) p++; if (*p == '.') { p++; if (!is_digit(*p)) return js_parse_error_pos(s, p, "Unterminated fractional number"); while (is_digit(*p)) p++; } if (*p == 'e' || *p == 'E') { p++; if (*p == '+' || *p == '-') p++; if (!is_digit(*p)) return js_parse_error_pos(s, p, "Exponent part is missing a number"); while (is_digit(*p)) p++; } d = js_atod((const char *)p_start, NULL, 10, 0, &atod_mem); done: s->token.val = TOK_NUMBER; s->token.u.num.val = JS_NewFloat64(s->ctx, d); *pp = p; return 0; } static __exception int json_next_token(JSParseState *s) { const uint8_t *p; int c; JSAtom atom; if (js_check_stack_overflow(s->ctx->rt, 0)) { return js_parse_error(s, "stack overflow"); } free_token(s, &s->token); p = s->last_ptr = s->buf_ptr; redo: s->token.ptr = p; c = *p; switch(c) { case 0: if (p >= s->buf_end) { s->token.val = TOK_EOF; } else { goto def_token; } break; case '\'': if (!s->ext_json) { /* JSON does not accept single quoted strings */ goto def_token; } /* fall through */ case '\"': p++; if (json_parse_string(s, &p, c)) goto fail; break; case '\r': /* accept DOS and MAC newline sequences */ if (p[1] == '\n') { p++; } /* fall thru */ case '\n': p++; goto redo; case '\f': case '\v': if (!s->ext_json) { /* JSONWhitespace does not match , nor */ goto def_token; } /* fall through */ case ' ': case '\t': p++; goto redo; case '/': if (!s->ext_json) { /* JSON does not accept comments */ goto def_token; } if (p[1] == '*') { /* comment */ p += 2; for(;;) { if (*p == '\0' && p >= s->buf_end) { js_parse_error(s, "unexpected end of comment"); goto fail; } if (p[0] == '*' && p[1] == '/') { p += 2; break; } if (*p >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); if (c == -1) { p++; /* skip invalid UTF-8 */ } } else { p++; } } goto redo; } else if (p[1] == '/') { /* line comment */ p += 2; for(;;) { if (*p == '\0' && p >= s->buf_end) break; if (*p == '\r' || *p == '\n') break; if (*p >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); /* LS or PS are considered as line terminator */ if (c == CP_LS || c == CP_PS) { break; } else if (c == -1) { p++; /* skip invalid UTF-8 */ } } else { p++; } } goto redo; } else { goto def_token; } break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '$': p++; atom = json_parse_ident(s, &p, c); if (atom == JS_ATOM_NULL) goto fail; s->token.u.ident.atom = atom; s->token.u.ident.has_escape = FALSE; s->token.u.ident.is_reserved = FALSE; s->token.val = TOK_IDENT; break; case '+': if (!s->ext_json) goto def_token; goto parse_number; case '.': if (s->ext_json && is_digit(p[1])) goto parse_number; else goto def_token; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* number */ parse_number: if (json_parse_number(s, &p)) goto fail; break; default: if (c >= 128) { js_parse_error(s, "unexpected character"); goto fail; } def_token: s->token.val = c; p++; break; } s->buf_ptr = p; // dump_token(s, &s->token); return 0; fail: s->token.val = TOK_ERROR; return -1; } static int match_identifier(const uint8_t *p, const char *s) { uint32_t c; while (*s) { if ((uint8_t)*s++ != *p++) return 0; } c = *p; if (c >= 128) c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); return !lre_js_is_ident_next(c); } /* simple_next_token() is used to check for the next token in simple cases. It is only used for ':' and '=>', 'let' or 'function' look-ahead. (*pp) is only set if TOK_IMPORT is returned for JS_DetectModule() Whitespace and comments are skipped correctly. Then the next token is analyzed, only for specific words. Return values: - '\n' if !no_line_terminator - TOK_ARROW, TOK_IN, TOK_IMPORT, TOK_OF, TOK_EXPORT, TOK_FUNCTION - TOK_IDENT is returned for other identifiers and keywords - otherwise the next character or unicode codepoint is returned. */ static int simple_next_token(const uint8_t **pp, BOOL no_line_terminator) { const uint8_t *p; uint32_t c; /* skip spaces and comments */ p = *pp; for (;;) { switch(c = *p++) { case '\r': case '\n': if (no_line_terminator) return '\n'; continue; case ' ': case '\t': case '\v': case '\f': continue; case '/': if (*p == '/') { if (no_line_terminator) return '\n'; while (*p && *p != '\r' && *p != '\n') p++; continue; } if (*p == '*') { while (*++p) { if ((*p == '\r' || *p == '\n') && no_line_terminator) return '\n'; if (*p == '*' && p[1] == '/') { p += 2; break; } } continue; } break; case '=': if (*p == '>') return TOK_ARROW; break; case 'i': if (match_identifier(p, "n")) return TOK_IN; if (match_identifier(p, "mport")) { *pp = p + 5; return TOK_IMPORT; } return TOK_IDENT; case 'o': if (match_identifier(p, "f")) return TOK_OF; return TOK_IDENT; case 'e': if (match_identifier(p, "xport")) return TOK_EXPORT; return TOK_IDENT; case 'f': if (match_identifier(p, "unction")) return TOK_FUNCTION; return TOK_IDENT; case '\\': if (*p == 'u') { if (lre_js_is_ident_first(lre_parse_escape(&p, TRUE))) return TOK_IDENT; } break; default: if (c >= 128) { c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p); if (no_line_terminator && (c == CP_PS || c == CP_LS)) return '\n'; } if (lre_is_space(c)) continue; if (lre_js_is_ident_first(c)) return TOK_IDENT; break; } return c; } } static int peek_token(JSParseState *s, BOOL no_line_terminator) { const uint8_t *p = s->buf_ptr; return simple_next_token(&p, no_line_terminator); } static inline int get_prev_opcode(JSFunctionDef *fd) { if (fd->last_opcode_pos < 0 || dbuf_error(&fd->byte_code)) return OP_invalid; else return fd->byte_code.buf[fd->last_opcode_pos]; } static BOOL js_is_live_code(JSParseState *s) { switch (get_prev_opcode(s->cur_func)) { case OP_tail_call: case OP_tail_call_method: case OP_return: case OP_return_undef: case OP_throw: case OP_throw_error: case OP_goto: #if SHORT_OPCODES case OP_goto8: case OP_goto16: #endif case OP_ret: return FALSE; default: return TRUE; } } static void emit_u8(JSParseState *s, uint8_t val) { dbuf_putc(&s->cur_func->byte_code, val); } static void emit_u16(JSParseState *s, uint16_t val) { dbuf_put_u16(&s->cur_func->byte_code, val); } static void emit_u32(JSParseState *s, uint32_t val) { dbuf_put_u32(&s->cur_func->byte_code, val); } static void emit_source_pos(JSParseState *s, const uint8_t *source_ptr) { JSFunctionDef *fd = s->cur_func; DynBuf *bc = &fd->byte_code; if (unlikely(fd->last_opcode_source_ptr != source_ptr)) { dbuf_putc(bc, OP_line_num); dbuf_put_u32(bc, source_ptr - s->buf_start); fd->last_opcode_source_ptr = source_ptr; } } static void emit_op(JSParseState *s, uint8_t val) { JSFunctionDef *fd = s->cur_func; DynBuf *bc = &fd->byte_code; fd->last_opcode_pos = bc->size; dbuf_putc(bc, val); } static void emit_atom(JSParseState *s, JSAtom name) { DynBuf *bc = &s->cur_func->byte_code; if (dbuf_realloc(bc, bc->size + 4)) return; /* not enough memory : don't duplicate the atom */ put_u32(bc->buf + bc->size, JS_DupAtom(s->ctx, name)); bc->size += 4; } static int update_label(JSFunctionDef *s, int label, int delta) { LabelSlot *ls; assert(label >= 0 && label < s->label_count); ls = &s->label_slots[label]; ls->ref_count += delta; assert(ls->ref_count >= 0); return ls->ref_count; } static int new_label_fd(JSFunctionDef *fd) { int label; LabelSlot *ls; if (js_resize_array(fd->ctx, (void *)&fd->label_slots, sizeof(fd->label_slots[0]), &fd->label_size, fd->label_count + 1)) return -1; label = fd->label_count++; ls = &fd->label_slots[label]; ls->ref_count = 0; ls->pos = -1; ls->pos2 = -1; ls->addr = -1; ls->first_reloc = NULL; return label; } static int new_label(JSParseState *s) { int label; label = new_label_fd(s->cur_func); if (unlikely(label < 0)) { dbuf_set_error(&s->cur_func->byte_code); } return label; } /* don't update the last opcode and don't emit line number info */ static void emit_label_raw(JSParseState *s, int label) { emit_u8(s, OP_label); emit_u32(s, label); s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; } /* return the label ID offset */ static int emit_label(JSParseState *s, int label) { if (label >= 0) { emit_op(s, OP_label); emit_u32(s, label); s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; return s->cur_func->byte_code.size - 4; } else { return -1; } } /* return label or -1 if dead code */ static int emit_goto(JSParseState *s, int opcode, int label) { if (js_is_live_code(s)) { if (label < 0) { label = new_label(s); if (label < 0) return -1; } emit_op(s, opcode); emit_u32(s, label); s->cur_func->label_slots[label].ref_count++; return label; } return -1; } /* return the constant pool index. 'val' is not duplicated. */ static int cpool_add(JSParseState *s, JSValue val) { JSFunctionDef *fd = s->cur_func; if (js_resize_array(s->ctx, (void *)&fd->cpool, sizeof(fd->cpool[0]), &fd->cpool_size, fd->cpool_count + 1)) return -1; fd->cpool[fd->cpool_count++] = val; return fd->cpool_count - 1; } static __exception int emit_push_const(JSParseState *s, JSValueConst val, BOOL as_atom) { int idx; if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING && as_atom) { JSAtom atom; /* warning: JS_NewAtomStr frees the string value */ JS_DupValue(s->ctx, val); atom = JS_NewAtomStr(s->ctx, JS_VALUE_GET_STRING(val)); if (atom != JS_ATOM_NULL && !__JS_AtomIsTaggedInt(atom)) { emit_op(s, OP_push_atom_value); emit_u32(s, atom); return 0; } } idx = cpool_add(s, JS_DupValue(s->ctx, val)); if (idx < 0) return -1; emit_op(s, OP_push_const); emit_u32(s, idx); return 0; } /* return the variable index or -1 if not found, add ARGUMENT_VAR_OFFSET for argument variables */ static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name) { int i; for(i = fd->arg_count; i-- > 0;) { if (fd->args[i].var_name == name) return i | ARGUMENT_VAR_OFFSET; } return -1; } static int find_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) { int i; for(i = fd->var_count; i-- > 0;) { if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0) return i; } return find_arg(ctx, fd, name); } /* return true if scope == parent_scope or if scope is a child of parent_scope */ static BOOL is_child_scope(JSContext *ctx, JSFunctionDef *fd, int scope, int parent_scope) { while (scope >= 0) { if (scope == parent_scope) return TRUE; scope = fd->scopes[scope].parent; } return FALSE; } /* find a 'var' declaration in the same scope or a child scope */ static int find_var_in_child_scope(JSContext *ctx, JSFunctionDef *fd, JSAtom name, int scope_level) { int i; for(i = 0; i < fd->var_count; i++) { JSVarDef *vd = &fd->vars[i]; if (vd->var_name == name && vd->scope_level == 0) { if (is_child_scope(ctx, fd, vd->scope_next, scope_level)) return i; } } return -1; } static JSGlobalVar *find_global_var(JSFunctionDef *fd, JSAtom name) { int i; for(i = 0; i < fd->global_var_count; i++) { JSGlobalVar *hf = &fd->global_vars[i]; if (hf->var_name == name) return hf; } return NULL; } static JSGlobalVar *find_lexical_global_var(JSFunctionDef *fd, JSAtom name) { JSGlobalVar *hf = find_global_var(fd, name); if (hf && hf->is_lexical) return hf; else return NULL; } static int find_lexical_decl(JSContext *ctx, JSFunctionDef *fd, JSAtom name, int scope_idx, BOOL check_catch_var) { while (scope_idx >= 0) { JSVarDef *vd = &fd->vars[scope_idx]; if (vd->var_name == name && (vd->is_lexical || (vd->var_kind == JS_VAR_CATCH && check_catch_var))) return scope_idx; scope_idx = vd->scope_next; } if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_GLOBAL) { if (find_lexical_global_var(fd, name)) return GLOBAL_VAR_OFFSET; } return -1; } static int push_scope(JSParseState *s) { if (s->cur_func) { JSFunctionDef *fd = s->cur_func; int scope = fd->scope_count; /* XXX: should check for scope overflow */ if ((fd->scope_count + 1) > fd->scope_size) { int new_size; size_t slack; JSVarScope *new_buf; /* XXX: potential arithmetic overflow */ new_size = max_int(fd->scope_count + 1, fd->scope_size * 3 / 2); if (fd->scopes == fd->def_scope_array) { new_buf = js_realloc2(s->ctx, NULL, new_size * sizeof(*fd->scopes), &slack); if (!new_buf) return -1; memcpy(new_buf, fd->scopes, fd->scope_count * sizeof(*fd->scopes)); } else { new_buf = js_realloc2(s->ctx, fd->scopes, new_size * sizeof(*fd->scopes), &slack); if (!new_buf) return -1; } new_size += slack / sizeof(*new_buf); fd->scopes = new_buf; fd->scope_size = new_size; } fd->scope_count++; fd->scopes[scope].parent = fd->scope_level; fd->scopes[scope].first = fd->scope_first; emit_op(s, OP_enter_scope); emit_u16(s, scope); return fd->scope_level = scope; } return 0; } static int get_first_lexical_var(JSFunctionDef *fd, int scope) { while (scope >= 0) { int scope_idx = fd->scopes[scope].first; if (scope_idx >= 0) return scope_idx; scope = fd->scopes[scope].parent; } return -1; } static void pop_scope(JSParseState *s) { if (s->cur_func) { /* disable scoped variables */ JSFunctionDef *fd = s->cur_func; int scope = fd->scope_level; emit_op(s, OP_leave_scope); emit_u16(s, scope); fd->scope_level = fd->scopes[scope].parent; fd->scope_first = get_first_lexical_var(fd, fd->scope_level); } } static void close_scopes(JSParseState *s, int scope, int scope_stop) { while (scope > scope_stop) { emit_op(s, OP_leave_scope); emit_u16(s, scope); scope = s->cur_func->scopes[scope].parent; } } /* return the variable index or -1 if error */ static int add_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) { JSVarDef *vd; /* the local variable indexes are currently stored on 16 bits */ if (fd->var_count >= JS_MAX_LOCAL_VARS) { JS_ThrowInternalError(ctx, "too many local variables"); return -1; } if (js_resize_array(ctx, (void **)&fd->vars, sizeof(fd->vars[0]), &fd->var_size, fd->var_count + 1)) return -1; vd = &fd->vars[fd->var_count++]; memset(vd, 0, sizeof(*vd)); vd->var_name = JS_DupAtom(ctx, name); vd->func_pool_idx = -1; return fd->var_count - 1; } static int add_scope_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name, JSVarKindEnum var_kind) { int idx = add_var(ctx, fd, name); if (idx >= 0) { JSVarDef *vd = &fd->vars[idx]; vd->var_kind = var_kind; vd->scope_level = fd->scope_level; vd->scope_next = fd->scope_first; fd->scopes[fd->scope_level].first = idx; fd->scope_first = idx; } return idx; } static int add_func_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) { int idx = fd->func_var_idx; if (idx < 0 && (idx = add_var(ctx, fd, name)) >= 0) { fd->func_var_idx = idx; fd->vars[idx].var_kind = JS_VAR_FUNCTION_NAME; fd->vars[idx].is_const = TRUE; } return idx; } static int add_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name) { JSVarDef *vd; /* the local variable indexes are currently stored on 16 bits */ if (fd->arg_count >= JS_MAX_LOCAL_VARS) { JS_ThrowInternalError(ctx, "too many arguments"); return -1; } if (js_resize_array(ctx, (void **)&fd->args, sizeof(fd->args[0]), &fd->arg_size, fd->arg_count + 1)) return -1; vd = &fd->args[fd->arg_count++]; memset(vd, 0, sizeof(*vd)); vd->var_name = JS_DupAtom(ctx, name); vd->func_pool_idx = -1; return fd->arg_count - 1; } /* add a global variable definition */ static JSGlobalVar *add_global_var(JSContext *ctx, JSFunctionDef *s, JSAtom name) { JSGlobalVar *hf; if (js_resize_array(ctx, (void **)&s->global_vars, sizeof(s->global_vars[0]), &s->global_var_size, s->global_var_count + 1)) return NULL; hf = &s->global_vars[s->global_var_count++]; hf->cpool_idx = -1; hf->force_init = FALSE; hf->is_lexical = FALSE; hf->is_const = FALSE; hf->scope_level = s->scope_level; hf->var_name = JS_DupAtom(ctx, name); return hf; } typedef enum { JS_VAR_DEF_WITH, JS_VAR_DEF_LET, JS_VAR_DEF_CONST, JS_VAR_DEF_FUNCTION_DECL, /* function declaration */ JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */ JS_VAR_DEF_CATCH, JS_VAR_DEF_VAR, } JSVarDefEnum; static int define_var(JSParseState *s, JSFunctionDef *fd, JSAtom name, JSVarDefEnum var_def_type) { JSContext *ctx = s->ctx; JSVarDef *vd; int idx; switch (var_def_type) { case JS_VAR_DEF_WITH: idx = add_scope_var(ctx, fd, name, JS_VAR_NORMAL); break; case JS_VAR_DEF_LET: case JS_VAR_DEF_CONST: case JS_VAR_DEF_FUNCTION_DECL: case JS_VAR_DEF_NEW_FUNCTION_DECL: idx = find_lexical_decl(ctx, fd, name, fd->scope_first, TRUE); if (idx >= 0) { if (idx < GLOBAL_VAR_OFFSET) { if (fd->vars[idx].scope_level == fd->scope_level) { goto redef_lex_error; } else if (fd->vars[idx].var_kind == JS_VAR_CATCH && (fd->vars[idx].scope_level + 2) == fd->scope_level) { goto redef_lex_error; } } else { if (fd->scope_level == fd->body_scope) { redef_lex_error: return js_parse_error(s, "invalid redefinition of lexical identifier"); } } } if (var_def_type != JS_VAR_DEF_FUNCTION_DECL && var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL && fd->scope_level == fd->body_scope && find_arg(ctx, fd, name) >= 0) { /* lexical variable redefines a parameter name */ return js_parse_error(s, "invalid redefinition of parameter name"); } if (find_var_in_child_scope(ctx, fd, name, fd->scope_level) >= 0) { return js_parse_error(s, "invalid redefinition of a variable"); } if (fd->is_global_var) { JSGlobalVar *hf; hf = find_global_var(fd, name); if (hf && is_child_scope(ctx, fd, hf->scope_level, fd->scope_level)) { return js_parse_error(s, "invalid redefinition of global identifier"); } } if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_GLOBAL && fd->scope_level == fd->body_scope) { JSGlobalVar *hf; hf = add_global_var(s->ctx, fd, name); if (!hf) return -1; hf->is_lexical = TRUE; hf->is_const = (var_def_type == JS_VAR_DEF_CONST); idx = GLOBAL_VAR_OFFSET; } else { JSVarKindEnum var_kind; if (var_def_type == JS_VAR_DEF_FUNCTION_DECL) var_kind = JS_VAR_FUNCTION_DECL; else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL) var_kind = JS_VAR_NEW_FUNCTION_DECL; else var_kind = JS_VAR_NORMAL; idx = add_scope_var(ctx, fd, name, var_kind); if (idx >= 0) { vd = &fd->vars[idx]; vd->is_lexical = 1; vd->is_const = (var_def_type == JS_VAR_DEF_CONST); } } break; case JS_VAR_DEF_CATCH: idx = add_scope_var(ctx, fd, name, JS_VAR_CATCH); break; case JS_VAR_DEF_VAR: if (find_lexical_decl(ctx, fd, name, fd->scope_first, FALSE) >= 0) { invalid_lexical_redefinition: /* error to redefine a var that inside a lexical scope */ return js_parse_error(s, "invalid redefinition of lexical identifier"); } if (fd->is_global_var) { JSGlobalVar *hf; hf = find_global_var(fd, name); hf = add_global_var(s->ctx, fd, name); if (!hf) return -1; idx = GLOBAL_VAR_OFFSET; } else { /* if the variable already exists, don't add it again */ idx = find_var(ctx, fd, name); if (idx >= 0) break; idx = add_var(ctx, fd, name); if (idx >= 0) { fd->vars[idx].scope_next = fd->scope_level; } } break; default: abort(); } return idx; } static __exception int js_parse_expr(JSParseState *s); static __exception int js_parse_function_decl(JSParseState *s, JSParseFunctionEnum func_type, JSAtom func_name, const uint8_t *ptr); static __exception int js_parse_function_decl2(JSParseState *s, JSParseFunctionEnum func_type, JSAtom func_name, const uint8_t *ptr, JSFunctionDef **pfd); static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags); static __exception int js_parse_assign_expr(JSParseState *s); static __exception int js_parse_unary(JSParseState *s, int parse_flags); static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, JSAtom label_name, int label_break, int label_cont, int drop_count); static void pop_break_entry(JSFunctionDef *fd); /* Note: all the fields are already sealed except length */ static int seal_template_obj(JSContext *ctx, JSValueConst obj) { JSObject *p; JSShapeProperty *prs; p = JS_VALUE_GET_OBJ(obj); prs = find_own_property1(p, JS_ATOM_length); if (prs) { if (js_update_property_flags(ctx, p, &prs, prs->flags & ~(JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE))) return -1; } p->extensible = FALSE; return 0; } static __exception int js_parse_template(JSParseState *s, int call, int *argc) { JSContext *ctx = s->ctx; JSValue raw_array, template_object; JSToken cooked; int depth, ret; raw_array = JS_NULL; /* avoid warning */ template_object = JS_NULL; /* avoid warning */ if (call) { /* Create a template object: an array of cooked strings */ /* Create an array of raw strings and store it to the raw property */ template_object = JS_NewArray(ctx); if (JS_IsException(template_object)) return -1; // pool_idx = s->cur_func->cpool_count; ret = emit_push_const(s, template_object, 0); JS_FreeValue(ctx, template_object); if (ret) return -1; raw_array = JS_NewArray(ctx); if (JS_IsException(raw_array)) return -1; if (JS_DefinePropertyValue(ctx, template_object, JS_ATOM_raw, raw_array, JS_PROP_THROW) < 0) { return -1; } } depth = 0; while (s->token.val == TOK_TEMPLATE) { const uint8_t *p = s->token.ptr + 1; cooked = s->token; if (call) { if (JS_DefinePropertyValueUint32(ctx, raw_array, depth, JS_DupValue(ctx, s->token.u.str.str), JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) { return -1; } /* re-parse the string with escape sequences but do not throw a syntax error if it contains invalid sequences */ if (js_parse_string(s, '`', FALSE, p, &cooked, &p)) { cooked.u.str.str = JS_NULL; } if (JS_DefinePropertyValueUint32(ctx, template_object, depth, cooked.u.str.str, JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) { return -1; } } else { JSString *str; /* re-parse the string with escape sequences and throw a syntax error if it contains invalid sequences */ JS_FreeValue(ctx, s->token.u.str.str); s->token.u.str.str = JS_NULL; if (js_parse_string(s, '`', TRUE, p, &cooked, &p)) return -1; str = JS_VALUE_GET_STRING(cooked.u.str.str); if (str->len != 0 || depth == 0) { ret = emit_push_const(s, cooked.u.str.str, 1); JS_FreeValue(s->ctx, cooked.u.str.str); if (ret) return -1; /* For single-literal templates, go directly to done1 */ if (depth == 0 && s->token.u.str.sep == '`') goto done1; depth++; } else { JS_FreeValue(s->ctx, cooked.u.str.str); } } if (s->token.u.str.sep == '`') goto done; if (next_token(s)) return -1; if (js_parse_expr(s)) return -1; depth++; if (s->token.val != '}') { return js_parse_error(s, "expected '}' after template expression"); } /* XXX: should convert to string at this stage? */ free_token(s, &s->token); /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; if (js_parse_template_part(s, s->buf_ptr)) return -1; } return js_parse_expect(s, TOK_TEMPLATE); done: if (call) { /* Seal the objects */ seal_template_obj(ctx, raw_array); seal_template_obj(ctx, template_object); *argc = depth + 1; } else if (depth > 1) { /* Use template_concat opcode to concatenate all parts */ emit_op(s, OP_template_concat); emit_u16(s, depth); } done1: return next_token(s); } #define PROP_TYPE_IDENT 0 #define PROP_TYPE_VAR 1 #define PROP_TYPE_GET 2 #define PROP_TYPE_SET 3 static BOOL token_is_ident(int tok) { /* Accept keywords and reserved words as property names */ return (tok == TOK_IDENT || (tok >= TOK_FIRST_KEYWORD && tok <= TOK_LAST_KEYWORD)); } /* if the property is an expression, name = JS_ATOM_NULL */ static int __exception js_parse_property_name(JSParseState *s, JSAtom *pname, BOOL allow_method, BOOL allow_var) { BOOL is_non_reserved_ident; JSAtom name; int prop_type; prop_type = PROP_TYPE_IDENT; if (allow_method) { /* if allow_private is true (for class field parsing) and get/set is following by ';' (or LF with ASI), then it is a field name */ if ((token_is_pseudo_keyword(s, JS_ATOM_get) || token_is_pseudo_keyword(s, JS_ATOM_set)) && (peek_token(s, TRUE) != '\n')) { /* get x(), set x() */ name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; if (s->token.val == ':' || s->token.val == ',' || s->token.val == '}' || s->token.val == '(' || s->token.val == '=' || (s->token.val == ';')) { is_non_reserved_ident = TRUE; goto ident_found; } prop_type = PROP_TYPE_GET + (name == JS_ATOM_set); JS_FreeAtom(s->ctx, name); } } if (token_is_ident(s->token.val)) { /* variable can only be a non-reserved identifier */ is_non_reserved_ident = (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved); /* keywords and reserved words have a valid atom */ name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; ident_found: if (is_non_reserved_ident && prop_type == PROP_TYPE_IDENT && allow_var) { if (!(s->token.val == ':' || (s->token.val == '(' && allow_method))) { prop_type = PROP_TYPE_VAR; } } } else if (s->token.val == TOK_STRING) { name = JS_ValueToAtom(s->ctx, s->token.u.str.str); if (name == JS_ATOM_NULL) goto fail; if (next_token(s)) goto fail1; } else if (s->token.val == TOK_NUMBER) { JSValue val; val = s->token.u.num.val; name = JS_ValueToAtom(s->ctx, val); if (name == JS_ATOM_NULL) goto fail; if (next_token(s)) goto fail1; } else if (s->token.val == '[') { if (next_token(s)) goto fail; if (js_parse_expr(s)) goto fail; if (js_parse_expect(s, ']')) goto fail; name = JS_ATOM_NULL; } else { goto invalid_prop; } if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR && s->token.val != '(') { JS_FreeAtom(s->ctx, name); invalid_prop: js_parse_error(s, "invalid property name"); goto fail; } *pname = name; return prop_type; fail1: JS_FreeAtom(s->ctx, name); fail: *pname = JS_ATOM_NULL; return -1; } typedef struct JSParsePos { BOOL got_lf; const uint8_t *ptr; } JSParsePos; static int js_parse_get_pos(JSParseState *s, JSParsePos *sp) { sp->ptr = s->token.ptr; sp->got_lf = s->got_lf; return 0; } static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp) { s->buf_ptr = sp->ptr; s->got_lf = sp->got_lf; return next_token(s); } /* return TRUE if a regexp literal is allowed after this token */ static BOOL is_regexp_allowed(int tok) { switch (tok) { case TOK_NUMBER: case TOK_STRING: case TOK_REGEXP: case TOK_DEC: case TOK_INC: case TOK_NULL: case TOK_FALSE: case TOK_TRUE: case TOK_THIS: case ')': case ']': case '}': /* XXX: regexp may occur after */ case TOK_IDENT: return FALSE; default: return TRUE; } } #define SKIP_HAS_SEMI (1 << 0) #define SKIP_HAS_ELLIPSIS (1 << 1) #define SKIP_HAS_ASSIGNMENT (1 << 2) static BOOL has_lf_in_range(const uint8_t *p1, const uint8_t *p2) { const uint8_t *tmp; if (p1 > p2) { tmp = p1; p1 = p2; p2 = tmp; } return (memchr(p1, '\n', p2 - p1) != NULL); } /* XXX: improve speed with early bailout */ /* XXX: no longer works if regexps are present. Could use previous regexp parsing heuristics to handle most cases */ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_terminator) { char state[256]; size_t level = 0; JSParsePos pos; int last_tok, tok = TOK_EOF; int c, tok_len, bits = 0; const uint8_t *last_token_ptr; /* protect from underflow */ state[level++] = 0; js_parse_get_pos(s, &pos); last_tok = 0; for (;;) { switch(s->token.val) { case '(': case '[': case '{': if (level >= sizeof(state)) goto done; state[level++] = s->token.val; break; case ')': if (state[--level] != '(') goto done; break; case ']': if (state[--level] != '[') goto done; break; case '}': c = state[--level]; if (c == '`') { /* continue the parsing of the template */ free_token(s, &s->token); /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; if (js_parse_template_part(s, s->buf_ptr)) goto done; goto handle_template; } else if (c != '{') { goto done; } break; case TOK_TEMPLATE: handle_template: if (s->token.u.str.sep != '`') { /* '${' inside the template : closing '}' and continue parsing the template */ if (level >= sizeof(state)) goto done; state[level++] = '`'; } break; case TOK_EOF: goto done; case ';': if (level == 2) { bits |= SKIP_HAS_SEMI; } break; case TOK_ELLIPSIS: if (level == 2) { bits |= SKIP_HAS_ELLIPSIS; } break; case '=': bits |= SKIP_HAS_ASSIGNMENT; break; case TOK_DIV_ASSIGN: tok_len = 2; goto parse_regexp; case '/': tok_len = 1; parse_regexp: if (is_regexp_allowed(last_tok)) { s->buf_ptr -= tok_len; if (js_parse_regexp(s)) { /* XXX: should clear the exception */ goto done; } } break; } /* last_tok is only used to recognize regexps */ if (s->token.val == TOK_IDENT && (token_is_pseudo_keyword(s, JS_ATOM_of) || token_is_pseudo_keyword(s, JS_ATOM_yield))) { last_tok = TOK_OF; } else { last_tok = s->token.val; } last_token_ptr = s->token.ptr; if (next_token(s)) { /* XXX: should clear the exception generated by next_token() */ break; } if (level <= 1) { tok = s->token.val; if (token_is_pseudo_keyword(s, JS_ATOM_of)) tok = TOK_OF; if (no_line_terminator && has_lf_in_range(last_token_ptr, s->token.ptr)) tok = '\n'; break; } } done: if (pbits) { *pbits = bits; } if (js_parse_seek_token(s, &pos)) return -1; return tok; } static void set_object_name(JSParseState *s, JSAtom name) { JSFunctionDef *fd = s->cur_func; int opcode; opcode = get_prev_opcode(fd); if (opcode == OP_set_name) { /* XXX: should free atom after OP_set_name? */ fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; emit_op(s, OP_set_name); emit_atom(s, name); } else if (opcode == OP_set_class_name) { int define_class_pos; JSAtom atom; define_class_pos = fd->last_opcode_pos + 1 - get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); assert(fd->byte_code.buf[define_class_pos] == OP_define_class); /* for consistency we free the previous atom which is JS_ATOM_empty_string */ atom = get_u32(fd->byte_code.buf + define_class_pos + 1); JS_FreeAtom(s->ctx, atom); put_u32(fd->byte_code.buf + define_class_pos + 1, JS_DupAtom(s->ctx, name)); fd->last_opcode_pos = -1; } } static void set_object_name_computed(JSParseState *s) { JSFunctionDef *fd = s->cur_func; int opcode; opcode = get_prev_opcode(fd); if (opcode == OP_set_name) { /* XXX: should free atom after OP_set_name? */ fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; emit_op(s, OP_set_name_computed); } else if (opcode == OP_set_class_name) { int define_class_pos; define_class_pos = fd->last_opcode_pos + 1 - get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); assert(fd->byte_code.buf[define_class_pos] == OP_define_class); fd->byte_code.buf[define_class_pos] = OP_define_class_computed; fd->last_opcode_pos = -1; } } static __exception int js_parse_object_literal(JSParseState *s) { JSAtom name = JS_ATOM_NULL; const uint8_t *start_ptr; int prop_type; BOOL has_proto; if (next_token(s)) goto fail; /* XXX: add an initial length that will be patched back */ emit_op(s, OP_object); has_proto = FALSE; while (s->token.val != '}') { /* specific case for getter/setter */ start_ptr = s->token.ptr; if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) return -1; if (js_parse_assign_expr(s)) return -1; emit_op(s, OP_null); /* dummy excludeList */ emit_op(s, OP_copy_data_properties); emit_u8(s, 2 | (1 << 2) | (0 << 5)); emit_op(s, OP_drop); /* pop excludeList */ emit_op(s, OP_drop); /* pop src object */ goto next; } prop_type = js_parse_property_name(s, &name, TRUE, TRUE); if (prop_type < 0) goto fail; if (prop_type == PROP_TYPE_VAR) { /* shortcut for x: x */ emit_op(s, OP_scope_get_var); emit_atom(s, name); emit_u16(s, s->cur_func->scope_level); emit_op(s, OP_define_field); emit_atom(s, name); } else if (s->token.val == '(') { BOOL is_getset = (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET); JSParseFunctionEnum func_type; int op_flags; if (is_getset) { func_type = JS_PARSE_FUNC_GETTER + prop_type - PROP_TYPE_GET; } else { func_type = JS_PARSE_FUNC_METHOD; } if (js_parse_function_decl(s, func_type, JS_ATOM_NULL, start_ptr)) goto fail; if (name == JS_ATOM_NULL) { emit_op(s, OP_define_method_computed); } else { emit_op(s, OP_define_method); emit_atom(s, name); } if (is_getset) { op_flags = OP_DEFINE_METHOD_GETTER + prop_type - PROP_TYPE_GET; } else { op_flags = OP_DEFINE_METHOD_METHOD; } emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE); } else { if (name == JS_ATOM_NULL) { /* must be done before evaluating expr */ emit_op(s, OP_to_propkey); } if (js_parse_expect(s, ':')) goto fail; if (js_parse_assign_expr(s)) goto fail; if (name == JS_ATOM_NULL) { set_object_name_computed(s); emit_op(s, OP_define_array_el); emit_op(s, OP_drop); } else { set_object_name(s, name); emit_op(s, OP_define_field); emit_atom(s, name); } } JS_FreeAtom(s->ctx, name); next: name = JS_ATOM_NULL; if (s->token.val != ',') break; if (next_token(s)) goto fail; } if (js_parse_expect(s, '}')) goto fail; return 0; fail: JS_FreeAtom(s->ctx, name); return -1; } /* allow the 'in' binary operator */ #define PF_IN_ACCEPTED (1 << 0) /* allow function calls parsing in js_parse_postfix_expr() */ #define PF_POSTFIX_CALL (1 << 1) /* allow the exponentiation operator in js_parse_unary() */ #define PF_POW_ALLOWED (1 << 2) /* forbid the exponentiation operator in js_parse_unary() */ #define PF_POW_FORBIDDEN (1 << 3) static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags); static JSFunctionDef *js_new_function_def(JSContext *ctx, JSFunctionDef *parent, BOOL is_eval, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache); static void emit_return(JSParseState *s, BOOL hasval); static __exception int js_parse_left_hand_side_expr(JSParseState *s) { return js_parse_postfix_expr(s, PF_POSTFIX_CALL); } static __exception int js_parse_array_literal(JSParseState *s) { uint32_t idx; BOOL need_length; if (next_token(s)) return -1; /* small regular arrays are created on the stack */ idx = 0; while (s->token.val != ']' && idx < 32) { if (s->token.val == ',' || s->token.val == TOK_ELLIPSIS) break; if (js_parse_assign_expr(s)) return -1; idx++; /* accept trailing comma */ if (s->token.val == ',') { if (next_token(s)) return -1; } else if (s->token.val != ']') goto done; } emit_op(s, OP_array_from); emit_u16(s, idx); /* larger arrays and holes are handled with explicit indices */ need_length = FALSE; while (s->token.val != ']' && idx < 0x7fffffff) { if (s->token.val == TOK_ELLIPSIS) break; need_length = TRUE; if (s->token.val != ',') { if (js_parse_assign_expr(s)) return -1; emit_op(s, OP_define_field); emit_u32(s, __JS_AtomFromUInt32(idx)); need_length = FALSE; } idx++; /* accept trailing comma */ if (s->token.val == ',') { if (next_token(s)) return -1; } } if (s->token.val == ']') { if (need_length) { /* Set the length: Cannot use OP_define_field because length is not configurable */ emit_op(s, OP_dup); emit_op(s, OP_push_i32); emit_u32(s, idx); emit_op(s, OP_put_field); emit_atom(s, JS_ATOM_length); } goto done; } /* huge arrays and spread elements require a dynamic index on the stack */ emit_op(s, OP_push_i32); emit_u32(s, idx); /* stack has array, index */ while (s->token.val != ']') { if (s->token.val == TOK_ELLIPSIS) { return js_parse_error(s, "spread syntax in array literals is not supported"); } else { need_length = TRUE; if (s->token.val != ',') { if (js_parse_assign_expr(s)) return -1; /* a idx val */ emit_op(s, OP_define_array_el); need_length = FALSE; } emit_op(s, OP_inc); } if (s->token.val != ',') break; if (next_token(s)) return -1; } if (need_length) { /* Set the length: cannot use OP_define_field because length is not configurable */ emit_op(s, OP_dup1); /* array length - array array length */ emit_op(s, OP_put_field); emit_atom(s, JS_ATOM_length); } else { emit_op(s, OP_drop); /* array length - array */ } done: return js_parse_expect(s, ']'); } /* XXX: remove */ static BOOL has_with_scope(JSFunctionDef *s, int scope_level) { /* check if scope chain contains a with statement */ while (s) { int scope_idx = s->scopes[scope_level].first; while (scope_idx >= 0) { JSVarDef *vd = &s->vars[scope_idx]; if (vd->var_name == JS_ATOM__with_) return TRUE; scope_idx = vd->scope_next; } /* check parent scopes */ scope_level = s->parent_scope_level; s = s->parent; } return FALSE; } static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope, JSAtom *pname, int *plabel, int *pdepth, BOOL keep, int tok) { JSFunctionDef *fd; int opcode, scope, label, depth; JSAtom name; /* we check the last opcode to get the lvalue type */ fd = s->cur_func; scope = 0; name = JS_ATOM_NULL; label = -1; depth = 0; switch(opcode = get_prev_opcode(fd)) { case OP_scope_get_var: name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); if (name == JS_ATOM_eval) { return js_parse_error(s, "invalid lvalue in strict mode"); } if (name == JS_ATOM_this || name == JS_ATOM_new_target) goto invalid_lvalue; depth = 2; /* will generate OP_get_ref_value */ break; case OP_get_field: name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); depth = 1; break; case OP_get_array_el: depth = 2; break; default: invalid_lvalue: if (tok == TOK_FOR) { return js_parse_error(s, "invalid for in/of left hand-side"); } else if (tok == TOK_INC || tok == TOK_DEC) { return js_parse_error(s, "invalid increment/decrement operand"); } else if (tok == '[' || tok == '{') { return js_parse_error(s, "invalid destructuring target"); } else { return js_parse_error(s, "invalid assignment left-hand side"); } } /* remove the last opcode */ fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; if (keep) { /* get the value but keep the object/fields on the stack */ switch(opcode) { case OP_scope_get_var: label = new_label(s); if (label < 0) return -1; emit_op(s, OP_scope_make_ref); emit_atom(s, name); emit_u32(s, label); emit_u16(s, scope); update_label(fd, label, 1); emit_op(s, OP_get_ref_value); opcode = OP_get_ref_value; break; case OP_get_field: emit_op(s, OP_get_field2); emit_atom(s, name); break; case OP_get_array_el: emit_op(s, OP_get_array_el3); break; default: abort(); } } else { switch(opcode) { case OP_scope_get_var: label = new_label(s); if (label < 0) return -1; emit_op(s, OP_scope_make_ref); emit_atom(s, name); emit_u32(s, label); emit_u16(s, scope); update_label(fd, label, 1); opcode = OP_get_ref_value; break; default: break; } } *popcode = opcode; *pscope = scope; /* name has refcount for OP_get_field and OP_get_ref_value, and JS_ATOM_NULL for other opcodes */ *pname = name; *plabel = label; if (pdepth) *pdepth = depth; return 0; } typedef enum { PUT_LVALUE_NOKEEP, /* [depth] v -> */ PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently just disable optimizations) */ PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ } PutLValueEnum; /* name has a live reference. 'is_let' is only used with opcode = OP_scope_get_var which is never generated by get_lvalue(). */ static void put_lvalue(JSParseState *s, int opcode, int scope, JSAtom name, int label, PutLValueEnum special, BOOL is_let) { switch(opcode) { case OP_get_field: /* depth = 1 */ switch(special) { case PUT_LVALUE_NOKEEP: case PUT_LVALUE_NOKEEP_DEPTH: break; case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert2); /* obj v -> v obj v */ break; case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */ break; case PUT_LVALUE_NOKEEP_BOTTOM: emit_op(s, OP_swap); break; default: abort(); } break; case OP_get_array_el: case OP_get_ref_value: /* depth = 2 */ if (opcode == OP_get_ref_value) { JS_FreeAtom(s->ctx, name); emit_label(s, label); } switch(special) { case PUT_LVALUE_NOKEEP: emit_op(s, OP_nop); /* will trigger optimization */ break; case PUT_LVALUE_NOKEEP_DEPTH: break; case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */ break; case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */ break; case PUT_LVALUE_NOKEEP_BOTTOM: emit_op(s, OP_rot3l); break; default: abort(); } break; default: break; } switch(opcode) { case OP_scope_get_var: /* val -- */ assert(special == PUT_LVALUE_NOKEEP || special == PUT_LVALUE_NOKEEP_DEPTH); emit_op(s, is_let ? OP_scope_put_var_init : OP_scope_put_var); emit_u32(s, name); /* has refcount */ emit_u16(s, scope); break; case OP_get_field: emit_op(s, OP_put_field); emit_u32(s, name); /* name has refcount */ break; case OP_get_array_el: emit_op(s, OP_put_array_el); break; case OP_get_ref_value: emit_op(s, OP_put_ref_value); break; default: abort(); } } static __exception int js_parse_expr_paren(JSParseState *s) { if (js_parse_expect(s, '(')) return -1; if (js_parse_expr(s)) return -1; if (js_parse_expect(s, ')')) return -1; return 0; } static int js_unsupported_keyword(JSParseState *s, JSAtom atom) { char buf[ATOM_GET_STR_BUF_SIZE]; return js_parse_error(s, "unsupported keyword: %s", JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom)); } static __exception int js_define_var(JSParseState *s, JSAtom name, int tok) { JSFunctionDef *fd = s->cur_func; JSVarDefEnum var_def_type; if (name == JS_ATOM_eval) { return js_parse_error(s, "invalid variable name in strict mode"); } if (name == JS_ATOM_let && tok == TOK_DEF) { return js_parse_error(s, "invalid lexical variable name"); } switch(tok) { case TOK_DEF: var_def_type = JS_VAR_DEF_CONST; break; case TOK_VAR: var_def_type = JS_VAR_DEF_LET; break; case TOK_CATCH: var_def_type = JS_VAR_DEF_CATCH; break; default: abort(); } if (define_var(s, fd, name, var_def_type) < 0) return -1; return 0; } static int js_parse_check_duplicate_parameter(JSParseState *s, JSAtom name) { /* Check for duplicate parameter names */ JSFunctionDef *fd = s->cur_func; int i; for (i = 0; i < fd->arg_count; i++) { if (fd->args[i].var_name == name) goto duplicate; } for (i = 0; i < fd->var_count; i++) { if (fd->vars[i].var_name == name) goto duplicate; } return 0; duplicate: return js_parse_error(s, "duplicate parameter names not allowed in this context"); } /* tok = TOK_VAR or TOK_DEF. Return whether a reference must be taken to the variable for proper 'with' or global variable evaluation */ /* Note: this function is needed only because variable references are not yet optimized in destructuring */ static BOOL need_var_reference(JSParseState *s, int tok) { JSFunctionDef *fd = s->cur_func; /* var now behaves like let - no reference needed */ return FALSE; } static JSAtom js_parse_destructuring_var(JSParseState *s, int tok, int is_arg) { JSAtom name; if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) || s->token.u.ident.atom == JS_ATOM_eval) { js_parse_error(s, "invalid destructuring target"); return JS_ATOM_NULL; } name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (is_arg && js_parse_check_duplicate_parameter(s, name)) goto fail; if (next_token(s)) goto fail; return name; fail: JS_FreeAtom(s->ctx, name); return JS_ATOM_NULL; } /* Return -1 if error, 0 if no initializer, 1 if an initializer is present at the top level. */ static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg, int hasval, int has_ellipsis, BOOL allow_initializer, BOOL export_flag) { int label_parse, label_assign, label_done, label_lvalue, depth_lvalue; int start_addr, assign_addr; JSAtom prop_name, var_name; int opcode, scope, tok1, skip_bits; BOOL has_initializer; if (has_ellipsis < 0) { /* pre-parse destructuration target for spread detection */ js_parse_skip_parens_token(s, &skip_bits, FALSE); has_ellipsis = skip_bits & SKIP_HAS_ELLIPSIS; } label_parse = new_label(s); label_assign = new_label(s); start_addr = s->cur_func->byte_code.size; if (hasval) { /* consume value from the stack */ emit_op(s, OP_dup); emit_op(s, OP_null); emit_op(s, OP_strict_eq); emit_goto(s, OP_if_true, label_parse); emit_label(s, label_assign); } else { emit_goto(s, OP_goto, label_parse); emit_label(s, label_assign); /* leave value on the stack */ emit_op(s, OP_dup); } assign_addr = s->cur_func->byte_code.size; if (s->token.val == '{') { if (next_token(s)) return -1; /* throw an exception if the value cannot be converted to an object */ emit_op(s, OP_to_object); if (has_ellipsis) { /* add excludeList on stack just below src object */ emit_op(s, OP_object); emit_op(s, OP_swap); } while (s->token.val != '}') { int prop_type; if (s->token.val == TOK_ELLIPSIS) { if (!has_ellipsis) { JS_ThrowInternalError(s->ctx, "unexpected ellipsis token"); return -1; } if (next_token(s)) return -1; if (tok) { var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) return -1; if (need_var_reference(s, tok)) { /* Must make a reference for proper `with` semantics */ emit_op(s, OP_scope_get_var); emit_atom(s, var_name); emit_u16(s, s->cur_func->scope_level); JS_FreeAtom(s->ctx, var_name); goto lvalue0; } else { opcode = OP_scope_get_var; scope = s->cur_func->scope_level; label_lvalue = -1; depth_lvalue = 0; } } else { if (js_parse_left_hand_side_expr(s)) return -1; lvalue0: if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) return -1; } if (s->token.val != '}') { js_parse_error(s, "assignment rest property must be last"); goto var_error; } emit_op(s, OP_object); /* target */ emit_op(s, OP_copy_data_properties); emit_u8(s, 0 | ((depth_lvalue + 1) << 2) | ((depth_lvalue + 2) << 5)); goto set_val; } prop_type = js_parse_property_name(s, &prop_name, FALSE, TRUE); if (prop_type < 0) return -1; var_name = JS_ATOM_NULL; if (prop_type == PROP_TYPE_IDENT) { if (next_token(s)) goto prop_error; if ((s->token.val == '[' || s->token.val == '{') && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' || tok1 == '=' || tok1 == '}')) { if (prop_name == JS_ATOM_NULL) { /* computed property name on stack */ if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_to_propkey); /* avoid calling ToString twice */ emit_op(s, OP_perm3); /* TOS: src excludeList prop */ emit_op(s, OP_null); /* TOS: src excludeList prop null */ emit_op(s, OP_define_array_el); /* TOS: src excludeList prop */ emit_op(s, OP_perm3); /* TOS: excludeList src prop */ } /* get the computed property from the source object */ emit_op(s, OP_get_array_el2); } else { /* named property */ if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_swap); /* TOS: src excludeList */ emit_op(s, OP_null); /* TOS: src excludeList null */ emit_op(s, OP_define_field); /* TOS: src excludeList */ emit_atom(s, prop_name); emit_op(s, OP_swap); /* TOS: excludeList src */ } /* get the named property from the source object */ emit_op(s, OP_get_field2); emit_u32(s, prop_name); } if (js_parse_destructuring_element(s, tok, is_arg, TRUE, -1, TRUE, export_flag) < 0) return -1; if (s->token.val == '}') break; /* accept a trailing comma before the '}' */ if (js_parse_expect(s, ',')) return -1; continue; } if (prop_name == JS_ATOM_NULL) { emit_op(s, OP_to_propkey); if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_perm3); emit_op(s, OP_null); emit_op(s, OP_define_array_el); emit_op(s, OP_perm3); } /* source prop -- source source prop */ emit_op(s, OP_dup1); } else { if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_swap); emit_op(s, OP_null); emit_op(s, OP_define_field); emit_atom(s, prop_name); emit_op(s, OP_swap); } /* source -- source source */ emit_op(s, OP_dup); } if (tok) { var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) goto prop_error; if (need_var_reference(s, tok)) { /* Must make a reference for proper `with` semantics */ emit_op(s, OP_scope_get_var); emit_atom(s, var_name); emit_u16(s, s->cur_func->scope_level); JS_FreeAtom(s->ctx, var_name); goto lvalue1; } else { /* no need to make a reference for let/const */ opcode = OP_scope_get_var; scope = s->cur_func->scope_level; label_lvalue = -1; depth_lvalue = 0; } } else { if (js_parse_left_hand_side_expr(s)) goto prop_error; lvalue1: if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) goto prop_error; /* swap ref and lvalue object if any */ if (prop_name == JS_ATOM_NULL) { switch(depth_lvalue) { case 1: /* source prop x -> x source prop */ emit_op(s, OP_rot3r); break; case 2: /* source prop x y -> x y source prop */ emit_op(s, OP_swap2); /* t p2 s p1 */ break; case 3: /* source prop x y z -> x y z source prop */ emit_op(s, OP_rot5l); emit_op(s, OP_rot5l); break; } } else { switch(depth_lvalue) { case 1: /* source x -> x source */ emit_op(s, OP_swap); break; case 2: /* source x y -> x y source */ emit_op(s, OP_rot3l); break; case 3: /* source x y z -> x y z source */ emit_op(s, OP_rot4l); break; } } } if (prop_name == JS_ATOM_NULL) { /* computed property name on stack */ /* XXX: should have OP_get_array_el2x with depth */ /* source prop -- val */ emit_op(s, OP_get_array_el); } else { /* named property */ /* XXX: should have OP_get_field2x with depth */ /* source -- val */ emit_op(s, OP_get_field); emit_u32(s, prop_name); } } else { /* prop_type = PROP_TYPE_VAR, cannot be a computed property */ if (is_arg && js_parse_check_duplicate_parameter(s, prop_name)) goto prop_error; if (prop_name == JS_ATOM_eval) { js_parse_error(s, "invalid destructuring target"); goto prop_error; } if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_swap); emit_op(s, OP_null); emit_op(s, OP_define_field); emit_atom(s, prop_name); emit_op(s, OP_swap); } if (!tok || need_var_reference(s, tok)) { /* generate reference */ /* source -- source source */ emit_op(s, OP_dup); emit_op(s, OP_scope_get_var); emit_atom(s, prop_name); emit_u16(s, s->cur_func->scope_level); goto lvalue1; } else { /* no need to make a reference for let/const */ var_name = JS_DupAtom(s->ctx, prop_name); opcode = OP_scope_get_var; scope = s->cur_func->scope_level; label_lvalue = -1; depth_lvalue = 0; /* source -- source val */ emit_op(s, OP_get_field2); emit_u32(s, prop_name); } } set_val: if (tok) { if (js_define_var(s, var_name, tok)) goto var_error; scope = s->cur_func->scope_level; /* XXX: check */ } if (s->token.val == '=') { /* handle optional default value */ int label_hasval; emit_op(s, OP_dup); emit_op(s, OP_null); emit_op(s, OP_strict_eq); label_hasval = emit_goto(s, OP_if_false, -1); if (next_token(s)) goto var_error; emit_op(s, OP_drop); if (js_parse_assign_expr(s)) goto var_error; if (opcode == OP_scope_get_var || opcode == OP_get_ref_value) set_object_name(s, var_name); emit_label(s, label_hasval); } /* store value into lvalue object */ put_lvalue(s, opcode, scope, var_name, label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, (tok == TOK_DEF || tok == TOK_VAR)); if (s->token.val == '}') break; /* accept a trailing comma before the '}' */ if (js_parse_expect(s, ',')) return -1; } /* drop the source object */ emit_op(s, OP_drop); if (has_ellipsis) { emit_op(s, OP_drop); /* pop excludeList */ } if (next_token(s)) return -1; } else if (s->token.val == '[') { return js_parse_error(s, "array destructuring is not supported"); } else { return js_parse_error(s, "invalid assignment syntax"); } if (s->token.val == '=' && allow_initializer) { label_done = emit_goto(s, OP_goto, -1); if (next_token(s)) return -1; emit_label(s, label_parse); if (hasval) emit_op(s, OP_drop); if (js_parse_assign_expr(s)) return -1; emit_goto(s, OP_goto, label_assign); emit_label(s, label_done); has_initializer = TRUE; } else { /* normally hasval is true except if js_parse_skip_parens_token() was wrong in the parsing */ // assert(hasval); if (!hasval) { js_parse_error(s, "too complicated destructuring expression"); return -1; } /* remove test and decrement label ref count */ memset(s->cur_func->byte_code.buf + start_addr, OP_nop, assign_addr - start_addr); s->cur_func->label_slots[label_parse].ref_count--; has_initializer = FALSE; } return has_initializer; prop_error: JS_FreeAtom(s->ctx, prop_name); var_error: JS_FreeAtom(s->ctx, var_name); return -1; } typedef enum FuncCallType { FUNC_CALL_NORMAL, FUNC_CALL_TEMPLATE, } FuncCallType; static void optional_chain_test(JSParseState *s, int *poptional_chaining_label, int drop_count) { int label_next, i; if (*poptional_chaining_label < 0) *poptional_chaining_label = new_label(s); /* XXX: could be more efficient with a specific opcode */ emit_op(s, OP_dup); emit_op(s, OP_is_null); label_next = emit_goto(s, OP_if_false, -1); for(i = 0; i < drop_count; i++) emit_op(s, OP_drop); emit_op(s, OP_null); emit_goto(s, OP_goto, *poptional_chaining_label); emit_label(s, label_next); } /* allowed parse_flags: PF_POSTFIX_CALL */ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) { FuncCallType call_type; int optional_chaining_label; BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; const uint8_t *op_token_ptr; call_type = FUNC_CALL_NORMAL; switch(s->token.val) { case TOK_NUMBER: { JSValue val; val = s->token.u.num.val; if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { emit_op(s, OP_push_i32); emit_u32(s, JS_VALUE_GET_INT(val)); } else { large_number: if (emit_push_const(s, val, 0) < 0) return -1; } } if (next_token(s)) return -1; break; case TOK_TEMPLATE: if (js_parse_template(s, 0, NULL)) return -1; break; case TOK_STRING: if (emit_push_const(s, s->token.u.str.str, 1)) return -1; if (next_token(s)) return -1; break; case TOK_DIV_ASSIGN: s->buf_ptr -= 2; goto parse_regexp; case '/': s->buf_ptr--; parse_regexp: { JSValue str; int ret; if (!s->ctx->compile_regexp) return js_parse_error(s, "RegExp are not supported"); /* the previous token is '/' or '/=', so no need to free */ if (js_parse_regexp(s)) return -1; ret = emit_push_const(s, s->token.u.regexp.body, 0); str = s->ctx->compile_regexp(s->ctx, s->token.u.regexp.body, s->token.u.regexp.flags); if (JS_IsException(str)) { /* add the line number info */ int line_num, col_num; line_num = get_line_col(&col_num, s->buf_start, s->token.ptr - s->buf_start); build_backtrace(s->ctx, s->ctx->rt->current_exception, s->filename, line_num + 1, col_num + 1, 0); return -1; } ret = emit_push_const(s, str, 0); JS_FreeValue(s->ctx, str); if (ret) return -1; /* we use a specific opcode to be sure the correct function is called (otherwise the bytecode would have to be verified by the RegExp constructor) */ emit_op(s, OP_regexp); if (next_token(s)) return -1; } break; case '(': if (js_parse_expr_paren(s)) return -1; break; case TOK_FUNCTION: if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_ATOM_NULL, s->token.ptr)) return -1; break; case TOK_NULL: if (next_token(s)) return -1; emit_op(s, OP_null); break; case TOK_THIS: if (next_token(s)) return -1; emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); break; case TOK_FALSE: if (next_token(s)) return -1; emit_op(s, OP_push_false); break; case TOK_TRUE: if (next_token(s)) return -1; emit_op(s, OP_push_true); break; case TOK_IDENT: { JSAtom name; const uint8_t *source_ptr; if (s->token.u.ident.is_reserved) { return js_parse_error_reserved_identifier(s); } source_ptr = s->token.ptr; name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) { JS_FreeAtom(s->ctx, name); return -1; } emit_source_pos(s, source_ptr); emit_op(s, OP_scope_get_var); emit_u32(s, name); emit_u16(s, s->cur_func->scope_level); } break; case '{': case '[': if (s->token.val == '{') { if (js_parse_object_literal(s)) return -1; } else { if (js_parse_array_literal(s)) return -1; } break; case TOK_NEW: return js_parse_error(s, "'new' keyword is not supported"); default: return js_parse_error(s, "unexpected token in expression: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); } optional_chaining_label = -1; for(;;) { JSFunctionDef *fd = s->cur_func; BOOL has_optional_chain = FALSE; if (s->token.val == TOK_QUESTION_MARK_DOT) { if ((parse_flags & PF_POSTFIX_CALL) == 0) return js_parse_error(s, "new keyword cannot be used with an optional chain"); op_token_ptr = s->token.ptr; /* optional chaining */ if (next_token(s)) return -1; has_optional_chain = TRUE; if (s->token.val == '(' && accept_lparen) { goto parse_func_call; } else if (s->token.val == '[') { goto parse_array_access; } else { goto parse_property; } } else if (s->token.val == TOK_TEMPLATE && call_type == FUNC_CALL_NORMAL) { if (optional_chaining_label >= 0) { return js_parse_error(s, "template literal cannot appear in an optional chain"); } call_type = FUNC_CALL_TEMPLATE; op_token_ptr = s->token.ptr; /* XXX: check if right position */ goto parse_func_call2; } else if (s->token.val == '(' && accept_lparen) { int opcode, arg_count, drop_count; /* function call */ parse_func_call: op_token_ptr = s->token.ptr; if (next_token(s)) return -1; if (call_type == FUNC_CALL_NORMAL) { parse_func_call2: switch(opcode = get_prev_opcode(fd)) { case OP_get_field: /* keep the object on the stack */ fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; drop_count = 2; break; case OP_get_field_opt_chain: { int opt_chain_label, next_label; opt_chain_label = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); /* keep the object on the stack */ fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; fd->byte_code.size = fd->last_opcode_pos + 1 + 4; next_label = emit_goto(s, OP_goto, -1); emit_label(s, opt_chain_label); /* need an additional undefined value for the case where the optional field does not exists */ emit_op(s, OP_null); emit_label(s, next_label); drop_count = 2; opcode = OP_get_field; } break; case OP_get_array_el: /* keep the object on the stack */ fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; drop_count = 2; break; case OP_get_array_el_opt_chain: { int opt_chain_label, next_label; opt_chain_label = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); /* keep the object on the stack */ fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; fd->byte_code.size = fd->last_opcode_pos + 1; next_label = emit_goto(s, OP_goto, -1); emit_label(s, opt_chain_label); /* need an additional undefined value for the case where the optional field does not exists */ emit_op(s, OP_null); emit_label(s, next_label); drop_count = 2; opcode = OP_get_array_el; } break; case OP_scope_get_var: { JSAtom name; int scope; name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL && !has_optional_chain) { /* direct 'eval' */ opcode = OP_eval; } else { /* verify if function name resolves to a simple get_loc/get_arg: a function call inside a `with` statement can resolve to a method call of the `with` context object */ /* XXX: always generate the OP_scope_get_ref and remove it in variable resolution pass ? */ if (has_with_scope(fd, scope)) { opcode = OP_scope_get_ref; fd->byte_code.buf[fd->last_opcode_pos] = opcode; } } drop_count = 1; } break; default: opcode = OP_invalid; drop_count = 1; break; } if (has_optional_chain) { optional_chain_test(s, &optional_chaining_label, drop_count); } } else { opcode = OP_invalid; } if (call_type == FUNC_CALL_TEMPLATE) { if (js_parse_template(s, 1, &arg_count)) return -1; goto emit_func_call; } /* parse arguments */ arg_count = 0; while (s->token.val != ')') { if (arg_count >= 65535) { return js_parse_error(s, "Too many call arguments"); } if (s->token.val == TOK_ELLIPSIS) return js_parse_error(s, "spread syntax in function calls is not supported"); if (js_parse_assign_expr(s)) return -1; arg_count++; if (s->token.val == ')') break; /* accept a trailing comma before the ')' */ if (js_parse_expect(s, ',')) return -1; } if (next_token(s)) return -1; emit_func_call: { emit_source_pos(s, op_token_ptr); switch(opcode) { case OP_get_field: case OP_get_array_el: case OP_scope_get_ref: emit_op(s, OP_call_method); emit_u16(s, arg_count); break; case OP_eval: emit_op(s, OP_eval); emit_u16(s, arg_count); emit_u16(s, fd->scope_level); fd->has_eval_call = TRUE; break; default: emit_op(s, OP_call); emit_u16(s, arg_count); break; } } call_type = FUNC_CALL_NORMAL; } else if (s->token.val == '.') { op_token_ptr = s->token.ptr; if (next_token(s)) return -1; parse_property: emit_source_pos(s, op_token_ptr); if (!token_is_ident(s->token.val)) { return js_parse_error(s, "expecting field name"); } if (has_optional_chain) { optional_chain_test(s, &optional_chaining_label, 1); } emit_op(s, OP_get_field); emit_atom(s, s->token.u.ident.atom); if (next_token(s)) return -1; } else if (s->token.val == '[') { op_token_ptr = s->token.ptr; parse_array_access: if (has_optional_chain) { optional_chain_test(s, &optional_chaining_label, 1); } if (next_token(s)) return -1; if (js_parse_expr(s)) return -1; if (js_parse_expect(s, ']')) return -1; emit_source_pos(s, op_token_ptr); emit_op(s, OP_get_array_el); } else { break; } } if (optional_chaining_label >= 0) { JSFunctionDef *fd = s->cur_func; int opcode; emit_label_raw(s, optional_chaining_label); /* modify the last opcode so that it is an indicator of an optional chain */ opcode = get_prev_opcode(fd); if (opcode == OP_get_field || opcode == OP_get_array_el) { if (opcode == OP_get_field) opcode = OP_get_field_opt_chain; else opcode = OP_get_array_el_opt_chain; fd->byte_code.buf[fd->last_opcode_pos] = opcode; } else { fd->last_opcode_pos = -1; } } return 0; } static __exception int js_parse_delete(JSParseState *s) { JSFunctionDef *fd = s->cur_func; JSAtom name; int opcode; if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_FORBIDDEN)) return -1; switch(opcode = get_prev_opcode(fd)) { case OP_get_field: case OP_get_field_opt_chain: { JSValue val; int ret, opt_chain_label, next_label; if (opcode == OP_get_field_opt_chain) { opt_chain_label = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); } else { opt_chain_label = -1; } name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); fd->byte_code.size = fd->last_opcode_pos; val = JS_AtomToValue(s->ctx, name); ret = emit_push_const(s, val, 1); JS_FreeValue(s->ctx, val); JS_FreeAtom(s->ctx, name); if (ret) return ret; emit_op(s, OP_delete); if (opt_chain_label >= 0) { next_label = emit_goto(s, OP_goto, -1); emit_label(s, opt_chain_label); /* if the optional chain is not taken, return 'true' */ emit_op(s, OP_drop); emit_op(s, OP_push_true); emit_label(s, next_label); } fd->last_opcode_pos = -1; } break; case OP_get_array_el: fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; emit_op(s, OP_delete); break; case OP_get_array_el_opt_chain: { int opt_chain_label, next_label; opt_chain_label = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); fd->byte_code.size = fd->last_opcode_pos; emit_op(s, OP_delete); next_label = emit_goto(s, OP_goto, -1); emit_label(s, opt_chain_label); /* if the optional chain is not taken, return 'true' */ emit_op(s, OP_drop); emit_op(s, OP_push_true); emit_label(s, next_label); fd->last_opcode_pos = -1; } break; case OP_scope_get_var: /* 'delete this': this is not a reference */ name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); if (name == JS_ATOM_this || name == JS_ATOM_new_target) goto ret_true; return js_parse_error(s, "cannot delete a direct reference in strict mode"); break; default: ret_true: emit_op(s, OP_drop); emit_op(s, OP_push_true); break; } return 0; } /* allowed parse_flags: PF_POW_ALLOWED, PF_POW_FORBIDDEN */ static __exception int js_parse_unary(JSParseState *s, int parse_flags) { int op; const uint8_t *op_token_ptr; switch(s->token.val) { case '+': case '-': case '!': case '~': case TOK_VOID: op_token_ptr = s->token.ptr; op = s->token.val; if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_FORBIDDEN)) return -1; switch(op) { case '-': emit_source_pos(s, op_token_ptr); emit_op(s, OP_neg); break; case '+': emit_source_pos(s, op_token_ptr); emit_op(s, OP_plus); break; case '!': emit_op(s, OP_lnot); break; case '~': emit_source_pos(s, op_token_ptr); emit_op(s, OP_not); break; case TOK_VOID: emit_op(s, OP_drop); emit_op(s, OP_null); break; default: abort(); } parse_flags = 0; break; case TOK_DEC: case TOK_INC: { int opcode, op, scope, label; JSAtom name; op = s->token.val; op_token_ptr = s->token.ptr; if (next_token(s)) return -1; if (js_parse_unary(s, 0)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; emit_source_pos(s, op_token_ptr); emit_op(s, OP_dec + op - TOK_DEC); put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); } break; case TOK_DELETE: if (js_parse_delete(s)) return -1; parse_flags = 0; break; default: if (js_parse_postfix_expr(s, PF_POSTFIX_CALL)) return -1; if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { int opcode, op, scope, label; JSAtom name; op = s->token.val; op_token_ptr = s->token.ptr; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; emit_source_pos(s, op_token_ptr); emit_op(s, OP_post_dec + op - TOK_DEC); put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, FALSE); if (next_token(s)) return -1; } break; } if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) { if (s->token.val == TOK_POW) { /* Strict ES7 exponentiation syntax rules: To solve conficting semantics between different implementations regarding the precedence of prefix operators and the postifx exponential, ES7 specifies that -2**2 is a syntax error. */ if (parse_flags & PF_POW_FORBIDDEN) { JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'"); return -1; } op_token_ptr = s->token.ptr; if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_ALLOWED)) return -1; emit_source_pos(s, op_token_ptr); emit_op(s, OP_pow); } } return 0; } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_expr_binary(JSParseState *s, int level, int parse_flags) { int op, opcode; const uint8_t *op_token_ptr; if (level == 0) { return js_parse_unary(s, PF_POW_ALLOWED); } else { if (js_parse_expr_binary(s, level - 1, parse_flags)) return -1; } for(;;) { op = s->token.val; op_token_ptr = s->token.ptr; switch(level) { case 1: switch(op) { case '*': opcode = OP_mul; break; case '/': opcode = OP_div; break; case '%': opcode = OP_mod; break; default: return 0; } break; case 2: switch(op) { case '+': opcode = OP_add; break; case '-': opcode = OP_sub; break; default: return 0; } break; case 3: switch(op) { case TOK_SHL: opcode = OP_shl; break; case TOK_SAR: opcode = OP_sar; break; case TOK_SHR: opcode = OP_shr; break; default: return 0; } break; case 4: switch(op) { case '<': opcode = OP_lt; break; case '>': opcode = OP_gt; break; case TOK_LTE: opcode = OP_lte; break; case TOK_GTE: opcode = OP_gte; break; case TOK_IN: if (parse_flags & PF_IN_ACCEPTED) { opcode = OP_in; } else { return 0; } break; default: return 0; } break; case 5: switch(op) { case TOK_STRICT_EQ: opcode = OP_strict_eq; break; case TOK_STRICT_NEQ: opcode = OP_strict_neq; break; default: return 0; } break; case 6: switch(op) { case '&': opcode = OP_and; break; default: return 0; } break; case 7: switch(op) { case '^': opcode = OP_xor; break; default: return 0; } break; case 8: switch(op) { case '|': opcode = OP_or; break; default: return 0; } break; default: abort(); } if (next_token(s)) return -1; if (js_parse_expr_binary(s, level - 1, parse_flags)) return -1; emit_source_pos(s, op_token_ptr); emit_op(s, opcode); } return 0; } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_logical_and_or(JSParseState *s, int op, int parse_flags) { int label1; if (op == TOK_LAND) { if (js_parse_expr_binary(s, 8, parse_flags)) return -1; } else { if (js_parse_logical_and_or(s, TOK_LAND, parse_flags)) return -1; } if (s->token.val == op) { label1 = new_label(s); for(;;) { if (next_token(s)) return -1; emit_op(s, OP_dup); emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, label1); emit_op(s, OP_drop); if (op == TOK_LAND) { if (js_parse_expr_binary(s, 8, parse_flags)) return -1; } else { if (js_parse_logical_and_or(s, TOK_LAND, parse_flags)) return -1; } if (s->token.val != op) { if (s->token.val == TOK_DOUBLE_QUESTION_MARK) return js_parse_error(s, "cannot mix ?? with && or ||"); break; } } emit_label(s, label1); } return 0; } static __exception int js_parse_coalesce_expr(JSParseState *s, int parse_flags) { int label1; if (js_parse_logical_and_or(s, TOK_LOR, parse_flags)) return -1; if (s->token.val == TOK_DOUBLE_QUESTION_MARK) { label1 = new_label(s); for(;;) { if (next_token(s)) return -1; emit_op(s, OP_dup); emit_op(s, OP_is_null); emit_goto(s, OP_if_false, label1); emit_op(s, OP_drop); if (js_parse_expr_binary(s, 8, parse_flags)) return -1; if (s->token.val != TOK_DOUBLE_QUESTION_MARK) break; } emit_label(s, label1); } return 0; } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_cond_expr(JSParseState *s, int parse_flags) { int label1, label2; if (js_parse_coalesce_expr(s, parse_flags)) return -1; if (s->token.val == '?') { if (next_token(s)) return -1; label1 = emit_goto(s, OP_if_false, -1); if (js_parse_assign_expr(s)) return -1; if (js_parse_expect(s, ':')) return -1; label2 = emit_goto(s, OP_goto, -1); emit_label(s, label1); if (js_parse_assign_expr2(s, parse_flags & PF_IN_ACCEPTED)) return -1; emit_label(s, label2); } return 0; } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) { int opcode, op, scope, skip_bits; JSAtom name0 = JS_ATOM_NULL; JSAtom name; if (s->token.val == '(' && js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) { return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_ATOM_NULL, s->token.ptr); } else if (s->token.val == TOK_IDENT && peek_token(s, TRUE) == TOK_ARROW) { return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_ATOM_NULL, s->token.ptr); } else if ((s->token.val == '{' || s->token.val == '[') && js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { if (js_parse_destructuring_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE, FALSE) < 0) return -1; return 0; } next: if (s->token.val == TOK_IDENT) { /* name0 is used to check for OP_set_name pattern, not duplicated */ name0 = s->token.u.ident.atom; } if (js_parse_cond_expr(s, parse_flags)) return -1; op = s->token.val; if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) { int label; const uint8_t *op_token_ptr; op_token_ptr = s->token.ptr; if (next_token(s)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, (op != '='), op) < 0) return -1; if (js_parse_assign_expr2(s, parse_flags)) { JS_FreeAtom(s->ctx, name); return -1; } if (op == '=') { if (opcode == OP_get_ref_value && name == name0) { set_object_name(s, name); } } else { static const uint8_t assign_opcodes[] = { OP_mul, OP_div, OP_mod, OP_add, OP_sub, OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or, OP_pow, }; op = assign_opcodes[op - TOK_MUL_ASSIGN]; emit_source_pos(s, op_token_ptr); emit_op(s, op); } put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); } else if (op >= TOK_LAND_ASSIGN && op <= TOK_DOUBLE_QUESTION_MARK_ASSIGN) { int label, label1, depth_lvalue, label2; if (next_token(s)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, &depth_lvalue, TRUE, op) < 0) return -1; emit_op(s, OP_dup); if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN) emit_op(s, OP_is_null); label1 = emit_goto(s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, -1); emit_op(s, OP_drop); if (js_parse_assign_expr2(s, parse_flags)) { JS_FreeAtom(s->ctx, name); return -1; } if (opcode == OP_get_ref_value && name == name0) { set_object_name(s, name); } switch(depth_lvalue) { case 1: emit_op(s, OP_insert2); break; case 2: emit_op(s, OP_insert3); break; case 3: emit_op(s, OP_insert4); break; default: abort(); } /* XXX: we disable the OP_put_ref_value optimization by not using put_lvalue() otherwise depth_lvalue is not correct */ put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH, FALSE); label2 = emit_goto(s, OP_goto, -1); emit_label(s, label1); /* remove the lvalue stack entries */ while (depth_lvalue != 0) { emit_op(s, OP_nip); depth_lvalue--; } emit_label(s, label2); } return 0; } static __exception int js_parse_assign_expr(JSParseState *s) { return js_parse_assign_expr2(s, PF_IN_ACCEPTED); } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_expr2(JSParseState *s, int parse_flags) { BOOL comma = FALSE; for(;;) { if (js_parse_assign_expr2(s, parse_flags)) return -1; if (comma) { /* prevent get_lvalue from using the last expression as an lvalue. This also prevents the conversion of of get_var to get_ref for method lookup in function call inside `with` statement. */ s->cur_func->last_opcode_pos = -1; } if (s->token.val != ',') break; comma = TRUE; if (next_token(s)) return -1; emit_op(s, OP_drop); } return 0; } static __exception int js_parse_expr(JSParseState *s) { return js_parse_expr2(s, PF_IN_ACCEPTED); } static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, JSAtom label_name, int label_break, int label_cont, int drop_count) { be->prev = fd->top_break; fd->top_break = be; be->label_name = label_name; be->label_break = label_break; be->label_cont = label_cont; be->drop_count = drop_count; be->label_finally = -1; be->scope_level = fd->scope_level; be->is_regular_stmt = FALSE; } static void pop_break_entry(JSFunctionDef *fd) { BlockEnv *be; be = fd->top_break; fd->top_break = be->prev; } static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont) { BlockEnv *top; int i, scope_level; scope_level = s->cur_func->scope_level; top = s->cur_func->top_break; while (top != NULL) { close_scopes(s, scope_level, top->scope_level); scope_level = top->scope_level; if (is_cont && top->label_cont != -1 && (name == JS_ATOM_NULL || top->label_name == name)) { /* continue stays inside the same block */ emit_goto(s, OP_goto, top->label_cont); return 0; } if (!is_cont && top->label_break != -1 && ((name == JS_ATOM_NULL && !top->is_regular_stmt) || top->label_name == name)) { emit_goto(s, OP_goto, top->label_break); return 0; } for(i = 0; i < top->drop_count; i++) emit_op(s, OP_drop); if (top->label_finally != -1) { /* must push dummy value to keep same stack depth */ emit_op(s, OP_null); emit_goto(s, OP_gosub, top->label_finally); emit_op(s, OP_drop); } top = top->prev; } if (name == JS_ATOM_NULL) { if (is_cont) return js_parse_error(s, "continue must be inside loop"); else return js_parse_error(s, "break must be inside loop or switch"); } else { return js_parse_error(s, "break/continue label not found"); } } /* execute the finally blocks before return */ static void emit_return(JSParseState *s, BOOL hasval) { BlockEnv *top; top = s->cur_func->top_break; while (top != NULL) { if (top->label_finally != -1) { if (!hasval) { emit_op(s, OP_null); hasval = TRUE; } /* Remove the stack elements up to and including the catch offset. When 'yield' is used in an expression we have no easy way to count them, so we use this specific instruction instead. */ emit_op(s, OP_nip_catch); /* execute the "finally" block */ emit_goto(s, OP_gosub, top->label_finally); } top = top->prev; } emit_op(s, hasval ? OP_return : OP_return_undef); } #define DECL_MASK_FUNC (1 << 0) /* allow normal function declaration */ /* ored with DECL_MASK_FUNC if function declarations are allowed with a label */ #define DECL_MASK_FUNC_WITH_LABEL (1 << 1) #define DECL_MASK_OTHER (1 << 2) /* all other declarations */ #define DECL_MASK_ALL (DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL | DECL_MASK_OTHER) static __exception int js_parse_statement_or_decl(JSParseState *s, int decl_mask); static __exception int js_parse_statement(JSParseState *s) { return js_parse_statement_or_decl(s, 0); } static __exception int js_parse_block(JSParseState *s) { if (js_parse_expect(s, '{')) return -1; if (s->token.val != '}') { push_scope(s); for(;;) { if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) return -1; if (s->token.val == '}') break; } pop_scope(s); } if (next_token(s)) return -1; return 0; } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_var(JSParseState *s, int parse_flags, int tok, BOOL export_flag) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; JSAtom name = JS_ATOM_NULL; for (;;) { if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { return js_parse_error_reserved_identifier(s); } name = JS_DupAtom(ctx, s->token.u.ident.atom); if (name == JS_ATOM_let && tok == TOK_DEF) { js_parse_error(s, "'let' is not a valid lexical identifier"); goto var_error; } if (next_token(s)) goto var_error; if (js_define_var(s, name, tok)) goto var_error; if (s->token.val == '=') { if (next_token(s)) goto var_error; if (need_var_reference(s, tok)) { /* Must make a reference for proper `with` semantics */ int opcode, scope, label; JSAtom name1; emit_op(s, OP_scope_get_var); emit_atom(s, name); emit_u16(s, fd->scope_level); if (get_lvalue(s, &opcode, &scope, &name1, &label, NULL, FALSE, '=') < 0) goto var_error; if (js_parse_assign_expr2(s, parse_flags)) { JS_FreeAtom(ctx, name1); goto var_error; } set_object_name(s, name); put_lvalue(s, opcode, scope, name1, label, PUT_LVALUE_NOKEEP, FALSE); } else { if (js_parse_assign_expr2(s, parse_flags)) goto var_error; set_object_name(s, name); emit_op(s, (tok == TOK_DEF || tok == TOK_VAR) ? OP_scope_put_var_init : OP_scope_put_var); emit_atom(s, name); emit_u16(s, fd->scope_level); } } else { if (tok == TOK_DEF) { js_parse_error(s, "missing initializer for const variable"); goto var_error; } if (tok == TOK_VAR) { /* initialize lexical variable upon entering its scope */ emit_op(s, OP_null); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } } JS_FreeAtom(ctx, name); } else { int skip_bits; if ((s->token.val == '[' || s->token.val == '{') && js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { emit_op(s, OP_null); if (js_parse_destructuring_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE, export_flag) < 0) return -1; } else { return js_parse_error(s, "variable name expected"); } } if (s->token.val != ',') break; if (next_token(s)) return -1; } return 0; var_error: JS_FreeAtom(ctx, name); return -1; } /* test if the current token is a label. Use simplistic look-ahead scanner */ static BOOL is_label(JSParseState *s) { return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved && peek_token(s, FALSE) == ':'); } /* test if the current token is a let keyword. Use simplistic look-ahead scanner */ static int is_let(JSParseState *s, int decl_mask) { int res = FALSE; const uint8_t *last_token_ptr; if (token_is_pseudo_keyword(s, JS_ATOM_let)) { JSParsePos pos; js_parse_get_pos(s, &pos); for (;;) { last_token_ptr = s->token.ptr; if (next_token(s)) { res = -1; break; } if (s->token.val == '[') { /* let [ is a syntax restriction: it never introduces an ExpressionStatement */ res = TRUE; break; } if (s->token.val == '{' || (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) || s->token.val == TOK_YIELD || s->token.val == TOK_AWAIT) { /* Check for possible ASI if not scanning for Declaration */ /* XXX: should also check that `{` introduces a BindingPattern, but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */ if (!has_lf_in_range(last_token_ptr, s->token.ptr) || (decl_mask & DECL_MASK_OTHER)) { res = TRUE; break; } break; } break; } if (js_parse_seek_token(s, &pos)) { res = -1; } } return res; } /* for-in and for-of loops are not supported */ static __exception int js_parse_for_in_of(JSParseState *s, int label_name) { return js_parse_error(s, "'for in' and 'for of' loops are not supported"); } static void set_eval_ret_undefined(JSParseState *s) { if (s->cur_func->eval_ret_idx >= 0) { emit_op(s, OP_null); emit_op(s, OP_put_loc); emit_u16(s, s->cur_func->eval_ret_idx); } } static __exception int js_parse_statement_or_decl(JSParseState *s, int decl_mask) { JSContext *ctx = s->ctx; JSAtom label_name; int tok; /* specific label handling */ /* XXX: support multiple labels on loop statements */ label_name = JS_ATOM_NULL; if (is_label(s)) { BlockEnv *be; label_name = JS_DupAtom(ctx, s->token.u.ident.atom); for (be = s->cur_func->top_break; be; be = be->prev) { if (be->label_name == label_name) { js_parse_error(s, "duplicate label name"); goto fail; } } if (next_token(s)) goto fail; if (js_parse_expect(s, ':')) goto fail; if (s->token.val != TOK_FOR && s->token.val != TOK_DO && s->token.val != TOK_WHILE) { /* labelled regular statement */ int label_break, mask; BlockEnv break_entry; label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1, 0); break_entry.is_regular_stmt = TRUE; mask = 0; if (js_parse_statement_or_decl(s, mask)) goto fail; emit_label(s, label_break); pop_break_entry(s->cur_func); goto done; } } switch(tok = s->token.val) { case '{': if (js_parse_block(s)) goto fail; break; case TOK_RETURN: { const uint8_t *op_token_ptr; if (s->cur_func->is_eval) { js_parse_error(s, "return not in a function"); goto fail; } op_token_ptr = s->token.ptr; if (next_token(s)) goto fail; if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { if (js_parse_expr(s)) goto fail; emit_source_pos(s, op_token_ptr); emit_return(s, TRUE); } else { emit_source_pos(s, op_token_ptr); emit_return(s, FALSE); } if (js_parse_expect_semi(s)) goto fail; } break; case TOK_THROW: { const uint8_t *op_token_ptr; op_token_ptr = s->token.ptr; if (next_token(s)) goto fail; if (s->got_lf) { js_parse_error(s, "line terminator not allowed after throw"); goto fail; } if (js_parse_expr(s)) goto fail; emit_source_pos(s, op_token_ptr); emit_op(s, OP_throw); if (js_parse_expect_semi(s)) goto fail; } break; case TOK_DEF: haslet: if (!(decl_mask & DECL_MASK_OTHER)) { js_parse_error(s, "lexical declarations can't appear in single-statement context"); goto fail; } /* fall thru */ case TOK_VAR: if (!(decl_mask & DECL_MASK_OTHER)) { js_parse_error(s, "lexical declarations can't appear in single-statement context"); goto fail; } if (next_token(s)) goto fail; if (js_parse_var(s, TRUE, tok, FALSE)) goto fail; if (js_parse_expect_semi(s)) goto fail; break; case TOK_IF: { int label1, label2, mask; if (next_token(s)) goto fail; /* create a new scope for `let f;if(1) function f(){}` */ push_scope(s); set_eval_ret_undefined(s); if (js_parse_expr_paren(s)) goto fail; label1 = emit_goto(s, OP_if_false, -1); mask = 0; if (js_parse_statement_or_decl(s, mask)) goto fail; if (s->token.val == TOK_ELSE) { label2 = emit_goto(s, OP_goto, -1); if (next_token(s)) goto fail; emit_label(s, label1); if (js_parse_statement_or_decl(s, mask)) goto fail; label1 = label2; } emit_label(s, label1); pop_scope(s); } break; case TOK_WHILE: { int label_cont, label_break; BlockEnv break_entry; label_cont = new_label(s); label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, label_cont, 0); if (next_token(s)) goto fail; set_eval_ret_undefined(s); emit_label(s, label_cont); if (js_parse_expr_paren(s)) goto fail; emit_goto(s, OP_if_false, label_break); if (js_parse_statement(s)) goto fail; emit_goto(s, OP_goto, label_cont); emit_label(s, label_break); pop_break_entry(s->cur_func); } break; case TOK_DO: { int label_cont, label_break, label1; BlockEnv break_entry; label_cont = new_label(s); label_break = new_label(s); label1 = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, label_cont, 0); if (next_token(s)) goto fail; emit_label(s, label1); set_eval_ret_undefined(s); if (js_parse_statement(s)) goto fail; emit_label(s, label_cont); if (js_parse_expect(s, TOK_WHILE)) goto fail; if (js_parse_expr_paren(s)) goto fail; /* Insert semicolon if missing */ if (s->token.val == ';') { if (next_token(s)) goto fail; } emit_goto(s, OP_if_true, label1); emit_label(s, label_break); pop_break_entry(s->cur_func); } break; case TOK_FOR: { int label_cont, label_break, label_body, label_test; int pos_cont, pos_body, block_scope_level; BlockEnv break_entry; int tok, bits; if (next_token(s)) goto fail; set_eval_ret_undefined(s); bits = 0; if (s->token.val == '(') { js_parse_skip_parens_token(s, &bits, FALSE); } if (js_parse_expect(s, '(')) goto fail; if (!(bits & SKIP_HAS_SEMI)) { /* parse for/in or for/of */ if (js_parse_for_in_of(s, label_name)) goto fail; break; } block_scope_level = s->cur_func->scope_level; /* create scope for the lexical variables declared in the initial, test and increment expressions */ push_scope(s); /* initial expression */ tok = s->token.val; if (tok != ';') { if (tok == TOK_VAR || tok == TOK_DEF) { if (next_token(s)) goto fail; if (js_parse_var(s, FALSE, tok, FALSE)) goto fail; } else { if (js_parse_expr2(s, FALSE)) goto fail; emit_op(s, OP_drop); } /* close the closures before the first iteration */ close_scopes(s, s->cur_func->scope_level, block_scope_level); } if (js_parse_expect(s, ';')) goto fail; label_test = new_label(s); label_cont = new_label(s); label_body = new_label(s); label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, label_cont, 0); /* test expression */ if (s->token.val == ';') { /* no test expression */ label_test = label_body; } else { emit_label(s, label_test); if (js_parse_expr(s)) goto fail; emit_goto(s, OP_if_false, label_break); } if (js_parse_expect(s, ';')) goto fail; if (s->token.val == ')') { /* no end expression */ break_entry.label_cont = label_cont = label_test; pos_cont = 0; /* avoid warning */ } else { /* skip the end expression */ emit_goto(s, OP_goto, label_body); pos_cont = s->cur_func->byte_code.size; emit_label(s, label_cont); if (js_parse_expr(s)) goto fail; emit_op(s, OP_drop); if (label_test != label_body) emit_goto(s, OP_goto, label_test); } if (js_parse_expect(s, ')')) goto fail; pos_body = s->cur_func->byte_code.size; emit_label(s, label_body); if (js_parse_statement(s)) goto fail; /* close the closures before the next iteration */ /* XXX: check continue case */ close_scopes(s, s->cur_func->scope_level, block_scope_level); if (OPTIMIZE && label_test != label_body && label_cont != label_test) { /* move the increment code here */ DynBuf *bc = &s->cur_func->byte_code; int chunk_size = pos_body - pos_cont; int offset = bc->size - pos_cont; int i; dbuf_realloc(bc, bc->size + chunk_size); dbuf_put(bc, bc->buf + pos_cont, chunk_size); memset(bc->buf + pos_cont, OP_nop, chunk_size); /* increment part ends with a goto */ s->cur_func->last_opcode_pos = bc->size - 5; /* relocate labels */ for (i = label_cont; i < s->cur_func->label_count; i++) { LabelSlot *ls = &s->cur_func->label_slots[i]; if (ls->pos >= pos_cont && ls->pos < pos_body) ls->pos += offset; } } else { emit_goto(s, OP_goto, label_cont); } emit_label(s, label_break); pop_break_entry(s->cur_func); pop_scope(s); } break; case TOK_BREAK: case TOK_CONTINUE: { int is_cont = s->token.val - TOK_BREAK; int label; if (next_token(s)) goto fail; if (!s->got_lf && s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) label = s->token.u.ident.atom; else label = JS_ATOM_NULL; if (emit_break(s, label, is_cont)) goto fail; if (label != JS_ATOM_NULL) { if (next_token(s)) goto fail; } if (js_parse_expect_semi(s)) goto fail; } break; case TOK_SWITCH: { int label_case, label_break, label1; int default_label_pos; BlockEnv break_entry; if (next_token(s)) goto fail; set_eval_ret_undefined(s); if (js_parse_expr_paren(s)) goto fail; push_scope(s); label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1, 1); if (js_parse_expect(s, '{')) goto fail; default_label_pos = -1; label_case = -1; while (s->token.val != '}') { if (s->token.val == TOK_CASE) { label1 = -1; if (label_case >= 0) { /* skip the case if needed */ label1 = emit_goto(s, OP_goto, -1); } emit_label(s, label_case); label_case = -1; for (;;) { /* parse a sequence of case clauses */ if (next_token(s)) goto fail; emit_op(s, OP_dup); if (js_parse_expr(s)) goto fail; if (js_parse_expect(s, ':')) goto fail; emit_op(s, OP_strict_eq); if (s->token.val == TOK_CASE) { label1 = emit_goto(s, OP_if_true, label1); } else { label_case = emit_goto(s, OP_if_false, -1); emit_label(s, label1); break; } } } else if (s->token.val == TOK_DEFAULT) { if (next_token(s)) goto fail; if (js_parse_expect(s, ':')) goto fail; if (default_label_pos >= 0) { js_parse_error(s, "duplicate default"); goto fail; } if (label_case < 0) { /* falling thru direct from switch expression */ label_case = emit_goto(s, OP_goto, -1); } /* Emit a dummy label opcode. Label will be patched after the end of the switch body. Do not use emit_label(s, 0) because it would clobber label 0 address, preventing proper optimizer operation. */ emit_op(s, OP_label); emit_u32(s, 0); default_label_pos = s->cur_func->byte_code.size - 4; } else { if (label_case < 0) { /* falling thru direct from switch expression */ js_parse_error(s, "invalid switch statement"); goto fail; } if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) goto fail; } } if (js_parse_expect(s, '}')) goto fail; if (default_label_pos >= 0) { /* Ugly patch for the the `default` label, shameful and risky */ put_u32(s->cur_func->byte_code.buf + default_label_pos, label_case); s->cur_func->label_slots[label_case].pos = default_label_pos + 4; } else { emit_label(s, label_case); } emit_label(s, label_break); emit_op(s, OP_drop); /* drop the switch expression */ pop_break_entry(s->cur_func); pop_scope(s); } break; case TOK_TRY: { int label_catch, label_catch2, label_finally, label_end; JSAtom name; BlockEnv block_env; set_eval_ret_undefined(s); if (next_token(s)) goto fail; label_catch = new_label(s); label_catch2 = new_label(s); label_finally = new_label(s); label_end = new_label(s); emit_goto(s, OP_catch, label_catch); push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 1); block_env.label_finally = label_finally; if (js_parse_block(s)) goto fail; pop_break_entry(s->cur_func); if (js_is_live_code(s)) { /* drop the catch offset */ emit_op(s, OP_drop); /* must push dummy value to keep same stack size */ emit_op(s, OP_null); emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_drop); emit_goto(s, OP_goto, label_end); } if (s->token.val == TOK_CATCH) { if (next_token(s)) goto fail; push_scope(s); /* catch variable */ emit_label(s, label_catch); if (s->token.val == '{') { /* support optional-catch-binding feature */ emit_op(s, OP_drop); /* pop the exception object */ } else { if (js_parse_expect(s, '(')) goto fail; if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) { if (s->token.val == '[' || s->token.val == '{') { /* catch variables behave like var (block scoped) */ if (js_parse_destructuring_element(s, TOK_VAR, 0, TRUE, -1, TRUE, FALSE) < 0) goto fail; } else { js_parse_error(s, "identifier expected"); goto fail; } } else { name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s) || js_define_var(s, name, TOK_CATCH) < 0) { JS_FreeAtom(ctx, name); goto fail; } /* store the exception value in the catch variable */ emit_op(s, OP_scope_put_var); emit_u32(s, name); emit_u16(s, s->cur_func->scope_level); } if (js_parse_expect(s, ')')) goto fail; } /* XXX: should keep the address to nop it out if there is no finally block */ emit_goto(s, OP_catch, label_catch2); push_scope(s); /* catch block */ push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 1); block_env.label_finally = label_finally; if (js_parse_block(s)) goto fail; pop_break_entry(s->cur_func); pop_scope(s); /* catch block */ pop_scope(s); /* catch variable */ if (js_is_live_code(s)) { /* drop the catch2 offset */ emit_op(s, OP_drop); /* XXX: should keep the address to nop it out if there is no finally block */ /* must push dummy value to keep same stack size */ emit_op(s, OP_null); emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_drop); emit_goto(s, OP_goto, label_end); } /* catch exceptions thrown in the catch block to execute the * finally clause and rethrow the exception */ emit_label(s, label_catch2); /* catch value is at TOS, no need to push undefined */ emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_throw); } else if (s->token.val == TOK_FINALLY) { /* finally without catch : execute the finally clause * and rethrow the exception */ emit_label(s, label_catch); /* catch value is at TOS, no need to push undefined */ emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_throw); } else { js_parse_error(s, "expecting catch or finally"); goto fail; } emit_label(s, label_finally); if (s->token.val == TOK_FINALLY) { int saved_eval_ret_idx = 0; /* avoid warning */ if (next_token(s)) goto fail; /* on the stack: ret_value gosub_ret_value */ push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 2); if (s->cur_func->eval_ret_idx >= 0) { /* 'finally' updates eval_ret only if not a normal termination */ saved_eval_ret_idx = add_var(s->ctx, s->cur_func, JS_ATOM__ret_); if (saved_eval_ret_idx < 0) goto fail; emit_op(s, OP_get_loc); emit_u16(s, s->cur_func->eval_ret_idx); emit_op(s, OP_put_loc); emit_u16(s, saved_eval_ret_idx); set_eval_ret_undefined(s); } if (js_parse_block(s)) goto fail; if (s->cur_func->eval_ret_idx >= 0) { emit_op(s, OP_get_loc); emit_u16(s, saved_eval_ret_idx); emit_op(s, OP_put_loc); emit_u16(s, s->cur_func->eval_ret_idx); } pop_break_entry(s->cur_func); } emit_op(s, OP_ret); emit_label(s, label_end); } break; case ';': /* empty statement */ if (next_token(s)) goto fail; break; case TOK_FUNCTION: /* ES6 Annex B.3.2 and B.3.3 semantics */ if (!(decl_mask & DECL_MASK_FUNC)) goto func_decl_error; if (!(decl_mask & DECL_MASK_OTHER) && peek_token(s, FALSE) == '*') goto func_decl_error; goto parse_func_var; case TOK_IDENT: if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); goto fail; } /* let keyword is no longer supported */ if (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION) { if (!(decl_mask & DECL_MASK_OTHER)) { func_decl_error: js_parse_error(s, "function declarations can't appear in single-statement context"); goto fail; } parse_func_var: if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR, JS_ATOM_NULL, s->token.ptr)) goto fail; break; } goto hasexpr; case TOK_DEBUGGER: /* currently no debugger, so just skip the keyword */ if (next_token(s)) goto fail; if (js_parse_expect_semi(s)) goto fail; break; case TOK_ENUM: case TOK_EXPORT: js_unsupported_keyword(s, s->token.u.ident.atom); goto fail; default: hasexpr: emit_source_pos(s, s->token.ptr); if (js_parse_expr(s)) goto fail; if (s->cur_func->eval_ret_idx >= 0) { /* store the expression value so that it can be returned by eval() */ emit_op(s, OP_put_loc); emit_u16(s, s->cur_func->eval_ret_idx); } else { emit_op(s, OP_drop); /* drop the result */ } if (js_parse_expect_semi(s)) goto fail; break; } done: JS_FreeAtom(ctx, label_name); return 0; fail: JS_FreeAtom(ctx, label_name); return -1; } static int add_closure_var(JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSAtom var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind); static __exception int js_parse_source_element(JSParseState *s) { if (s->token.val == TOK_FUNCTION || (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION)) { if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_ATOM_NULL, s->token.ptr)) return -1; } else { if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) return -1; } return 0; } static JSFunctionDef *js_new_function_def(JSContext *ctx, JSFunctionDef *parent, BOOL is_eval, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache) { 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->strip_debug = ((ctx->rt->strip_flags & JS_STRIP_DEBUG) != 0); fd->strip_source = ((ctx->rt->strip_flags & (JS_STRIP_DEBUG | JS_STRIP_SOURCE)) != 0); 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->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; /* 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->source_pos = source_ptr - get_line_col_cache->buf_start; fd->get_line_col_cache = get_line_col_cache; js_dbuf_init(ctx, &fd->pc2line); //fd->pc2line_last_line_num = line_num; //fd->pc2line_last_pc = 0; fd->last_opcode_source_ptr = source_ptr; return fd; } static void free_bytecode_atoms(JSRuntime *rt, const uint8_t *bc_buf, int bc_len, BOOL use_short_opcodes) { int pos, len, op; JSAtom atom; const JSOpCode *oi; pos = 0; while (pos < bc_len) { op = bc_buf[pos]; if (use_short_opcodes) oi = &short_opcode_info(op); else oi = &opcode_info[op]; len = oi->size; switch(oi->fmt) { case OP_FMT_atom: case OP_FMT_atom_u8: case OP_FMT_atom_u16: case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: if ((pos + 1 + 4) > bc_len) break; /* may happen if there is not enough memory when emiting bytecode */ atom = get_u32(bc_buf + pos + 1); JS_FreeAtomRT(rt, atom); break; default: break; } pos += len; } } static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd) { int i; struct list_head *el, *el1; /* free the child functions */ list_for_each_safe(el, el1, &fd->child_list) { JSFunctionDef *fd1; fd1 = list_entry(el, JSFunctionDef, link); js_free_function_def(ctx, fd1); } free_bytecode_atoms(ctx->rt, fd->byte_code.buf, fd->byte_code.size, fd->use_short_opcodes); dbuf_free(&fd->byte_code); js_free(ctx, fd->jump_slots); js_free(ctx, fd->label_slots); js_free(ctx, fd->line_number_slots); for(i = 0; i < fd->cpool_count; i++) { JS_FreeValue(ctx, fd->cpool[i]); } js_free(ctx, fd->cpool); JS_FreeAtom(ctx, fd->func_name); for(i = 0; i < fd->var_count; i++) { JS_FreeAtom(ctx, fd->vars[i].var_name); } js_free(ctx, fd->vars); for(i = 0; i < fd->arg_count; i++) { JS_FreeAtom(ctx, fd->args[i].var_name); } js_free(ctx, fd->args); for(i = 0; i < fd->global_var_count; i++) { JS_FreeAtom(ctx, fd->global_vars[i].var_name); } js_free(ctx, fd->global_vars); for(i = 0; i < fd->closure_var_count; i++) { JSClosureVar *cv = &fd->closure_var[i]; JS_FreeAtom(ctx, cv->var_name); } js_free(ctx, fd->closure_var); if (fd->scopes != fd->def_scope_array) js_free(ctx, fd->scopes); JS_FreeAtom(ctx, fd->filename); dbuf_free(&fd->pc2line); js_free(ctx, fd->source); if (fd->parent) { /* remove in parent list */ list_del(&fd->link); } js_free(ctx, fd); } #ifdef DUMP_BYTECODE static const char *skip_lines(const char *p, int n) { while (n-- > 0 && *p) { while (*p && *p++ != '\n') continue; } return p; } static void print_lines(const char *source, int line, int line1) { const char *s = source; const char *p = skip_lines(s, line); if (*p) { while (line++ < line1) { p = skip_lines(s = p, 1); printf(";; %.*s", (int)(p - s), s); if (!*p) { if (p[-1] != '\n') printf("\n"); break; } } } } static void dump_byte_code(JSContext *ctx, int pass, const uint8_t *tab, int len, const JSVarDef *args, int arg_count, const JSVarDef *vars, int var_count, const JSClosureVar *closure_var, int closure_var_count, const JSValue *cpool, uint32_t cpool_count, const char *source, const LabelSlot *label_slots, JSFunctionBytecode *b) { const JSOpCode *oi; int pos, pos_next, op, size, idx, addr, line, line1, in_source, line_num; uint8_t *bits = js_mallocz(ctx, len * sizeof(*bits)); BOOL use_short_opcodes = (b != NULL); if (b) { int col_num; line_num = find_line_num(ctx, b, -1, &col_num); } /* scan for jump targets */ for (pos = 0; pos < len; pos = pos_next) { op = tab[pos]; if (use_short_opcodes) oi = &short_opcode_info(op); else oi = &opcode_info[op]; pos_next = pos + oi->size; if (op < OP_COUNT) { switch (oi->fmt) { #if SHORT_OPCODES case OP_FMT_label8: pos++; addr = (int8_t)tab[pos]; goto has_addr; case OP_FMT_label16: pos++; addr = (int16_t)get_u16(tab + pos); goto has_addr; #endif case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: pos += 4; /* fall thru */ case OP_FMT_label: case OP_FMT_label_u16: pos++; addr = get_u32(tab + pos); goto has_addr; has_addr: if (pass == 1) addr = label_slots[addr].pos; if (pass == 2) addr = label_slots[addr].pos2; if (pass == 3) addr += pos; if (addr >= 0 && addr < len) bits[addr] |= 1; break; } } } in_source = 0; if (source) { /* Always print first line: needed if single line */ print_lines(source, 0, 1); in_source = 1; } line1 = line = 1; pos = 0; while (pos < len) { op = tab[pos]; if (source && b) { int col_num; if (b) { line1 = find_line_num(ctx, b, pos, &col_num) - line_num + 1; } else if (op == OP_line_num) { /* XXX: no longer works */ line1 = get_u32(tab + pos + 1) - line_num + 1; } if (line1 > line) { if (!in_source) printf("\n"); in_source = 1; print_lines(source, line, line1); line = line1; //bits[pos] |= 2; } } if (in_source) printf("\n"); in_source = 0; if (op >= OP_COUNT) { printf("invalid opcode (0x%02x)\n", op); pos++; continue; } if (use_short_opcodes) oi = &short_opcode_info(op); else oi = &opcode_info[op]; size = oi->size; if (pos + size > len) { printf("truncated opcode (0x%02x)\n", op); break; } #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 16) { int i, x, x0; x = x0 = printf("%5d ", pos); for (i = 0; i < size; i++) { if (i == 6) { printf("\n%*s", x = x0, ""); } x += printf(" %02X", tab[pos + i]); } printf("%*s", x0 + 20 - x, ""); } #endif if (bits[pos]) { printf("%5d: ", pos); } else { printf(" "); } printf("%s", oi->name); pos++; switch(oi->fmt) { case OP_FMT_none_int: printf(" %d", op - OP_push_0); break; case OP_FMT_npopx: printf(" %d", op - OP_call0); break; case OP_FMT_u8: printf(" %u", get_u8(tab + pos)); break; case OP_FMT_i8: printf(" %d", get_i8(tab + pos)); break; case OP_FMT_u16: case OP_FMT_npop: printf(" %u", get_u16(tab + pos)); break; case OP_FMT_npop_u16: printf(" %u,%u", get_u16(tab + pos), get_u16(tab + pos + 2)); break; case OP_FMT_i16: printf(" %d", get_i16(tab + pos)); break; case OP_FMT_i32: printf(" %d", get_i32(tab + pos)); break; case OP_FMT_u32: printf(" %u", get_u32(tab + pos)); break; #if SHORT_OPCODES case OP_FMT_label8: addr = get_i8(tab + pos); goto has_addr1; case OP_FMT_label16: addr = get_i16(tab + pos); goto has_addr1; #endif case OP_FMT_label: addr = get_u32(tab + pos); goto has_addr1; has_addr1: if (pass == 1) printf(" %u:%u", addr, label_slots[addr].pos); if (pass == 2) printf(" %u:%u", addr, label_slots[addr].pos2); if (pass == 3) printf(" %u", addr + pos); break; case OP_FMT_label_u16: addr = get_u32(tab + pos); if (pass == 1) printf(" %u:%u", addr, label_slots[addr].pos); if (pass == 2) printf(" %u:%u", addr, label_slots[addr].pos2); if (pass == 3) printf(" %u", addr + pos); printf(",%u", get_u16(tab + pos + 4)); break; #if SHORT_OPCODES case OP_FMT_const8: idx = get_u8(tab + pos); goto has_pool_idx; #endif case OP_FMT_const: idx = get_u32(tab + pos); goto has_pool_idx; has_pool_idx: printf(" %u: ", idx); if (idx < cpool_count) { JS_PrintValue(ctx, js_dump_value_write, stdout, cpool[idx], NULL); } break; case OP_FMT_atom: printf(" "); print_atom(ctx, get_u32(tab + pos)); break; case OP_FMT_atom_u8: printf(" "); print_atom(ctx, get_u32(tab + pos)); printf(",%d", get_u8(tab + pos + 4)); break; case OP_FMT_atom_u16: printf(" "); print_atom(ctx, get_u32(tab + pos)); printf(",%d", get_u16(tab + pos + 4)); break; case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: printf(" "); print_atom(ctx, get_u32(tab + pos)); addr = get_u32(tab + pos + 4); if (pass == 1) printf(",%u:%u", addr, label_slots[addr].pos); if (pass == 2) printf(",%u:%u", addr, label_slots[addr].pos2); if (pass == 3) printf(",%u", addr + pos + 4); if (oi->fmt == OP_FMT_atom_label_u8) printf(",%u", get_u8(tab + pos + 8)); else printf(",%u", get_u16(tab + pos + 8)); break; case OP_FMT_none_loc: idx = (op - OP_get_loc0) % 4; goto has_loc; case OP_FMT_loc8: idx = get_u8(tab + pos); goto has_loc; case OP_FMT_loc: idx = get_u16(tab + pos); has_loc: printf(" %d: ", idx); if (idx < var_count) { print_atom(ctx, vars[idx].var_name); } break; case OP_FMT_none_arg: idx = (op - OP_get_arg0) % 4; goto has_arg; case OP_FMT_arg: idx = get_u16(tab + pos); has_arg: printf(" %d: ", idx); if (idx < arg_count) { print_atom(ctx, args[idx].var_name); } break; case OP_FMT_none_var_ref: idx = (op - OP_get_var_ref0) % 4; goto has_var_ref; case OP_FMT_var_ref: idx = get_u16(tab + pos); has_var_ref: printf(" %d: ", idx); if (idx < closure_var_count) { print_atom(ctx, closure_var[idx].var_name); } break; default: break; } printf("\n"); pos += oi->size - 1; } if (source) { if (!in_source) printf("\n"); print_lines(source, line, INT32_MAX); } js_free(ctx, bits); } static __maybe_unused void dump_pc2line(JSContext *ctx, const uint8_t *buf, int len) { const uint8_t *p_end, *p; int pc, v, line_num, col_num, ret; unsigned int op; uint32_t val; if (len <= 0) return; printf("%5s %5s %5s\n", "PC", "LINE", "COL"); p = buf; p_end = buf + len; /* get the function line and column numbers */ ret = get_leb128(&val, p, p_end); if (ret < 0) goto fail; p += ret; line_num = val + 1; ret = get_leb128(&val, p, p_end); if (ret < 0) goto fail; p += ret; col_num = val + 1; printf("%5s %5d %5d\n", "-", line_num, col_num); pc = 0; while (p < p_end) { op = *p++; if (op == 0) { ret = get_leb128(&val, p, p_end); if (ret < 0) goto fail; pc += val; p += ret; ret = get_sleb128(&v, p, p_end); if (ret < 0) goto fail; p += ret; line_num += v; } else { op -= PC2LINE_OP_FIRST; pc += (op / PC2LINE_RANGE); line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE; } ret = get_sleb128(&v, p, p_end); if (ret < 0) goto fail; p += ret; col_num += v; printf("%5d %5d %5d\n", pc, line_num, col_num); } fail: ; } static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionBytecode *b) { int i; char atom_buf[ATOM_GET_STR_BUF_SIZE]; const char *str; if (b->has_debug && b->debug.filename != JS_ATOM_NULL) { int line_num, col_num; str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->debug.filename); line_num = find_line_num(ctx, b, -1, &col_num); printf("%s:%d:%d: ", str, line_num, col_num); } str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name); printf("function: %s%s\n", "", str); if (b->js_mode) { printf(" mode:"); printf(" strict"); printf("\n"); } if (b->arg_count && b->vardefs) { printf(" args:"); for(i = 0; i < b->arg_count; i++) { printf(" %s", JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->vardefs[i].var_name)); } printf("\n"); } if (b->var_count && b->vardefs) { printf(" locals:\n"); for(i = 0; i < b->var_count; i++) { JSVarDef *vd = &b->vardefs[b->arg_count + i]; printf("%5d: %s %s", i, vd->var_kind == JS_VAR_CATCH ? "catch" : (vd->var_kind == JS_VAR_FUNCTION_DECL || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" : vd->is_const ? "const" : vd->is_lexical ? "let" : "var", JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), vd->var_name)); if (vd->scope_level) printf(" [level:%d next:%d]", vd->scope_level, vd->scope_next); printf("\n"); } } if (b->closure_var_count) { printf(" closure vars:\n"); for(i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; printf("%5d: %s %s:%s%d %s\n", i, JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), cv->var_name), cv->is_local ? "local" : "parent", cv->is_arg ? "arg" : "loc", cv->var_idx, cv->is_const ? "const" : cv->is_lexical ? "let" : "var"); } } printf(" stack_size: %d\n", b->stack_size); printf(" opcodes:\n"); dump_byte_code(ctx, 3, b->byte_code_buf, b->byte_code_len, b->vardefs, b->arg_count, b->vardefs ? b->vardefs + b->arg_count : NULL, b->var_count, b->closure_var, b->closure_var_count, b->cpool, b->cpool_count, b->has_debug ? b->debug.source : NULL, NULL, b); #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32) if (b->has_debug) dump_pc2line(ctx, b->debug.pc2line_buf, b->debug.pc2line_len); #endif printf("\n"); } #endif static int add_closure_var(JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSAtom var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { JSClosureVar *cv; /* the closure variable indexes are currently stored on 16 bits */ if (s->closure_var_count >= JS_MAX_LOCAL_VARS) { JS_ThrowInternalError(ctx, "too many closure variables"); return -1; } if (js_resize_array(ctx, (void **)&s->closure_var, sizeof(s->closure_var[0]), &s->closure_var_size, s->closure_var_count + 1)) return -1; cv = &s->closure_var[s->closure_var_count++]; cv->is_local = is_local; cv->is_arg = is_arg; cv->is_const = is_const; cv->is_lexical = is_lexical; cv->var_kind = var_kind; cv->var_idx = var_idx; cv->var_name = JS_DupAtom(ctx, var_name); return s->closure_var_count - 1; } /* 'fd' must be a parent of 's'. Create in 's' a closure referencing a local variable (is_local = TRUE) or a closure (is_local = FALSE) in 'fd' */ static int get_closure_var2(JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_local, BOOL is_arg, int var_idx, JSAtom var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { int i; if (fd != s->parent) { var_idx = get_closure_var2(ctx, s->parent, fd, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); if (var_idx < 0) return -1; is_local = FALSE; } for(i = 0; i < s->closure_var_count; i++) { JSClosureVar *cv = &s->closure_var[i]; if (cv->var_idx == var_idx && cv->is_arg == is_arg && cv->is_local == is_local) return i; } return add_closure_var(ctx, s, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); } static int get_closure_var(JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_arg, int var_idx, JSAtom var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { return get_closure_var2(ctx, s, fd, TRUE, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); } static BOOL can_opt_put_ref_value(const uint8_t *bc_buf, int pos) { int opcode = bc_buf[pos]; return (bc_buf[pos + 1] == OP_put_ref_value && (opcode == OP_insert3 || opcode == OP_perm4 || opcode == OP_nop || opcode == OP_rot3l)); } static BOOL can_opt_put_global_ref_value(const uint8_t *bc_buf, int pos) { int opcode = bc_buf[pos]; return (bc_buf[pos + 1] == OP_put_ref_value && (opcode == OP_insert3 || opcode == OP_perm4 || opcode == OP_nop || opcode == OP_rot3l)); } static int optimize_scope_make_ref(JSContext *ctx, JSFunctionDef *s, DynBuf *bc, uint8_t *bc_buf, LabelSlot *ls, int pos_next, int get_op, int var_idx) { int label_pos, end_pos, pos; /* XXX: should optimize `loc(a) += expr` as `expr add_loc(a)` but only if expr does not modify `a`. should scan the code between pos_next and label_pos for operations that can potentially change `a`: OP_scope_make_ref(a), function calls, jumps and gosub. */ /* replace the reference get/put with normal variable accesses */ if (bc_buf[pos_next] == OP_get_ref_value) { dbuf_putc(bc, get_op); dbuf_put_u16(bc, var_idx); pos_next++; } /* remove the OP_label to make room for replacement */ /* label should have a refcount of 0 anyway */ /* XXX: should avoid this patch by inserting nops in phase 1 */ label_pos = ls->pos; pos = label_pos - 5; assert(bc_buf[pos] == OP_label); /* label points to an instruction pair: - insert3 / put_ref_value - perm4 / put_ref_value - rot3l / put_ref_value - nop / put_ref_value */ end_pos = label_pos + 2; if (bc_buf[label_pos] == OP_insert3) bc_buf[pos++] = OP_dup; bc_buf[pos] = get_op + 1; put_u16(bc_buf + pos + 1, var_idx); pos += 3; /* pad with OP_nop */ while (pos < end_pos) bc_buf[pos++] = OP_nop; return pos_next; } static int optimize_scope_make_global_ref(JSContext *ctx, JSFunctionDef *s, DynBuf *bc, uint8_t *bc_buf, LabelSlot *ls, int pos_next, JSAtom var_name) { int label_pos, end_pos, pos, op; /* replace the reference get/put with normal variable accesses */ /* need to check if the variable exists before evaluating the right expression */ /* XXX: need an extra OP_true if destructuring an array */ dbuf_putc(bc, OP_check_var); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); if (bc_buf[pos_next] == OP_get_ref_value) { dbuf_putc(bc, OP_get_var); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); pos_next++; } /* remove the OP_label to make room for replacement */ /* label should have a refcount of 0 anyway */ /* XXX: should have emitted several OP_nop to avoid this kludge */ label_pos = ls->pos; pos = label_pos - 5; assert(bc_buf[pos] == OP_label); end_pos = label_pos + 2; op = bc_buf[label_pos]; if (op != OP_nop) { switch(op) { case OP_insert3: op = OP_insert2; break; case OP_perm4: op = OP_perm3; break; case OP_rot3l: op = OP_swap; break; default: abort(); } bc_buf[pos++] = op; } bc_buf[pos] = OP_put_var_strict; /* XXX: need 1 extra OP_drop if destructuring an array */ put_u32(bc_buf + pos + 1, JS_DupAtom(ctx, var_name)); pos += 5; /* pad with OP_nop */ while (pos < end_pos) bc_buf[pos++] = OP_nop; return pos_next; } static int add_var_this(JSContext *ctx, JSFunctionDef *fd) { int idx; idx = add_var(ctx, fd, JS_ATOM_this); if (idx >= 0 && fd->is_derived_class_constructor) { JSVarDef *vd = &fd->vars[idx]; /* XXX: should have is_this flag or var type */ vd->is_lexical = 1; /* used to trigger 'uninitialized' checks in a derived class constructor */ } return idx; } static int resolve_pseudo_var(JSContext *ctx, JSFunctionDef *s, JSAtom var_name) { int var_idx; if (!s->has_this_binding) return -1; switch(var_name) { case JS_ATOM_this_active_func: /* 'this.active_func' pseudo variable */ if (s->this_active_func_var_idx < 0) s->this_active_func_var_idx = add_var(ctx, s, var_name); var_idx = s->this_active_func_var_idx; break; case JS_ATOM_new_target: /* 'new.target' pseudo variable */ if (s->new_target_var_idx < 0) s->new_target_var_idx = add_var(ctx, s, var_name); var_idx = s->new_target_var_idx; break; case JS_ATOM_this: /* 'this' pseudo variable */ if (s->this_var_idx < 0) s->this_var_idx = add_var_this(ctx, s); var_idx = s->this_var_idx; break; default: var_idx = -1; break; } return var_idx; } /* test if 'var_name' is in the variable object on the stack. If is it the case, handle it and jump to 'label_done' */ static void var_object_test(JSContext *ctx, JSFunctionDef *s, JSAtom var_name, int op, DynBuf *bc, int *plabel_done, BOOL is_with) { dbuf_putc(bc, op); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); } /* return the position of the next opcode */ static int resolve_scope_var(JSContext *ctx, JSFunctionDef *s, JSAtom var_name, int scope_level, int op, DynBuf *bc, uint8_t *bc_buf, LabelSlot *ls, int pos_next) { int idx, var_idx, is_put; int label_done; JSFunctionDef *fd; JSVarDef *vd; BOOL is_pseudo_var, is_arg_scope; label_done = -1; /* XXX: could be simpler to use a specific function to resolve the pseudo variables */ is_pseudo_var = (var_name == JS_ATOM_home_object || var_name == JS_ATOM_this_active_func || var_name == JS_ATOM_new_target || var_name == JS_ATOM_this); /* resolve local scoped variables */ var_idx = -1; for (idx = s->scopes[scope_level].first; idx >= 0;) { vd = &s->vars[idx]; if (vd->var_name == var_name) { if (op == OP_scope_put_var || op == OP_scope_make_ref) { if (vd->is_const) { dbuf_putc(bc, OP_throw_error); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_putc(bc, JS_THROW_VAR_RO); goto done; } } var_idx = idx; break; } idx = vd->scope_next; } is_arg_scope = (idx == ARG_SCOPE_END); if (var_idx < 0) { /* argument scope: variables are not visible but pseudo variables are visible */ if (!is_arg_scope) { var_idx = find_var(ctx, s, var_name); } if (var_idx < 0 && is_pseudo_var) var_idx = resolve_pseudo_var(ctx, s, var_name); if (var_idx < 0 && s->is_func_expr && var_name == s->func_name) { /* add a new variable with the function name */ var_idx = add_func_var(ctx, s, var_name); } } if (var_idx >= 0) { if ((op == OP_scope_put_var || op == OP_scope_make_ref) && !(var_idx & ARGUMENT_VAR_OFFSET) && s->vars[var_idx].is_const) { /* only happens when assigning a function expression name in strict mode */ dbuf_putc(bc, OP_throw_error); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_putc(bc, JS_THROW_VAR_RO); goto done; } /* OP_scope_put_var_init is only used to initialize a lexical variable, so it is never used in a with or var object. It can be used with a closure (module global variable case). */ switch (op) { case OP_scope_make_ref: if (!(var_idx & ARGUMENT_VAR_OFFSET) && s->vars[var_idx].var_kind == JS_VAR_FUNCTION_NAME) { /* Create a dummy object reference for the func_var */ dbuf_putc(bc, OP_object); dbuf_putc(bc, OP_get_loc); dbuf_put_u16(bc, var_idx); dbuf_putc(bc, OP_define_field); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_putc(bc, OP_push_atom_value); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); } else if (label_done == -1 && can_opt_put_ref_value(bc_buf, ls->pos)) { int get_op; if (var_idx & ARGUMENT_VAR_OFFSET) { get_op = OP_get_arg; var_idx -= ARGUMENT_VAR_OFFSET; } else { if (s->vars[var_idx].is_lexical) get_op = OP_get_loc_check; else get_op = OP_get_loc; } pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls, pos_next, get_op, var_idx); } else { /* Create a dummy object with a named slot that is a reference to the local variable */ if (var_idx & ARGUMENT_VAR_OFFSET) { dbuf_putc(bc, OP_make_arg_ref); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET); } else { dbuf_putc(bc, OP_make_loc_ref); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_put_u16(bc, var_idx); } } break; case OP_scope_get_ref: dbuf_putc(bc, OP_null); /* fall thru */ case OP_scope_get_var_checkthis: case OP_scope_get_var_undef: case OP_scope_get_var: case OP_scope_put_var: case OP_scope_put_var_init: is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init); if (var_idx & ARGUMENT_VAR_OFFSET) { dbuf_putc(bc, OP_get_arg + is_put); dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET); } else { if (is_put) { if (s->vars[var_idx].is_lexical) { if (op == OP_scope_put_var_init) { /* 'this' can only be initialized once */ if (var_name == JS_ATOM_this) dbuf_putc(bc, OP_put_loc_check_init); else dbuf_putc(bc, OP_put_loc); } else { dbuf_putc(bc, OP_put_loc_check); } } else { dbuf_putc(bc, OP_put_loc); } } else { if (s->vars[var_idx].is_lexical) { if (op == OP_scope_get_var_checkthis) { /* only used for 'this' return in derived class constructors */ dbuf_putc(bc, OP_get_loc_checkthis); } else { dbuf_putc(bc, OP_get_loc_check); } } else { dbuf_putc(bc, OP_get_loc); } } dbuf_put_u16(bc, var_idx); } break; case OP_scope_delete_var: dbuf_putc(bc, OP_push_false); break; } goto done; } /* check eval object */ if (!is_arg_scope && s->var_object_idx >= 0 && !is_pseudo_var) { dbuf_putc(bc, OP_get_loc); dbuf_put_u16(bc, s->var_object_idx); var_object_test(ctx, s, var_name, op, bc, &label_done, 0); } /* check eval object in argument scope */ if (s->arg_var_object_idx >= 0 && !is_pseudo_var) { dbuf_putc(bc, OP_get_loc); dbuf_put_u16(bc, s->arg_var_object_idx); var_object_test(ctx, s, var_name, op, bc, &label_done, 0); } /* check parent scopes */ for (fd = s; fd->parent;) { scope_level = fd->parent_scope_level; fd = fd->parent; for (idx = fd->scopes[scope_level].first; idx >= 0;) { vd = &fd->vars[idx]; if (vd->var_name == var_name) { if (op == OP_scope_put_var || op == OP_scope_make_ref) { if (vd->is_const) { dbuf_putc(bc, OP_throw_error); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_putc(bc, JS_THROW_VAR_RO); goto done; } } var_idx = idx; break; } idx = vd->scope_next; } is_arg_scope = (idx == ARG_SCOPE_END); if (var_idx >= 0) break; if (!is_arg_scope) { var_idx = find_var(ctx, fd, var_name); if (var_idx >= 0) break; } if (is_pseudo_var) { var_idx = resolve_pseudo_var(ctx, fd, var_name); if (var_idx >= 0) break; } if (fd->is_func_expr && fd->func_name == var_name) { /* add a new variable with the function name */ var_idx = add_func_var(ctx, fd, var_name); break; } /* check eval object */ if (!is_arg_scope && fd->var_object_idx >= 0 && !is_pseudo_var) { vd = &fd->vars[fd->var_object_idx]; vd->is_captured = 1; idx = get_closure_var(ctx, s, fd, FALSE, fd->var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL); dbuf_putc(bc, OP_get_var_ref); dbuf_put_u16(bc, idx); var_object_test(ctx, s, var_name, op, bc, &label_done, 0); } /* check eval object in argument scope */ if (fd->arg_var_object_idx >= 0 && !is_pseudo_var) { vd = &fd->vars[fd->arg_var_object_idx]; vd->is_captured = 1; idx = get_closure_var(ctx, s, fd, FALSE, fd->arg_var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL); dbuf_putc(bc, OP_get_var_ref); dbuf_put_u16(bc, idx); var_object_test(ctx, s, var_name, op, bc, &label_done, 0); } if (fd->is_eval) break; /* it it necessarily the top level function */ } /* check direct eval scope (in the closure of the eval function which is necessarily at the top level) */ if (!fd) fd = s; if (var_idx < 0 && fd->is_eval) { int idx1; for (idx1 = 0; idx1 < fd->closure_var_count; idx1++) { JSClosureVar *cv = &fd->closure_var[idx1]; if (var_name == cv->var_name) { if (fd != s) { idx = get_closure_var2(ctx, s, fd, FALSE, cv->is_arg, idx1, cv->var_name, cv->is_const, cv->is_lexical, cv->var_kind); } else { idx = idx1; } goto has_idx; } else if ((cv->var_name == JS_ATOM__var_ || cv->var_name == JS_ATOM__arg_var_) && !is_pseudo_var) { if (fd != s) { idx = get_closure_var2(ctx, s, fd, FALSE, cv->is_arg, idx1, cv->var_name, FALSE, FALSE, JS_VAR_NORMAL); } else { idx = idx1; } dbuf_putc(bc, OP_get_var_ref); dbuf_put_u16(bc, idx); var_object_test(ctx, s, var_name, op, bc, &label_done, 0); } } } if (var_idx >= 0) { /* find the corresponding closure variable */ if (var_idx & ARGUMENT_VAR_OFFSET) { fd->args[var_idx - ARGUMENT_VAR_OFFSET].is_captured = 1; idx = get_closure_var(ctx, s, fd, TRUE, var_idx - ARGUMENT_VAR_OFFSET, var_name, FALSE, FALSE, JS_VAR_NORMAL); } else { fd->vars[var_idx].is_captured = 1; idx = get_closure_var(ctx, s, fd, FALSE, var_idx, var_name, fd->vars[var_idx].is_const, fd->vars[var_idx].is_lexical, fd->vars[var_idx].var_kind); } if (idx >= 0) { has_idx: if ((op == OP_scope_put_var || op == OP_scope_make_ref) && s->closure_var[idx].is_const) { dbuf_putc(bc, OP_throw_error); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_putc(bc, JS_THROW_VAR_RO); goto done; } switch (op) { case OP_scope_make_ref: if (s->closure_var[idx].var_kind == JS_VAR_FUNCTION_NAME) { /* Create a dummy object reference for the func_var */ dbuf_putc(bc, OP_object); dbuf_putc(bc, OP_get_var_ref); dbuf_put_u16(bc, idx); dbuf_putc(bc, OP_define_field); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_putc(bc, OP_push_atom_value); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); } else if (label_done == -1 && can_opt_put_ref_value(bc_buf, ls->pos)) { int get_op; if (s->closure_var[idx].is_lexical) get_op = OP_get_var_ref_check; else get_op = OP_get_var_ref; pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls, pos_next, get_op, idx); } else { /* Create a dummy object with a named slot that is a reference to the closure variable */ dbuf_putc(bc, OP_make_var_ref_ref); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); dbuf_put_u16(bc, idx); } break; case OP_scope_get_ref: /* XXX: should create a dummy object with a named slot that is a reference to the closure variable */ dbuf_putc(bc, OP_null); /* fall thru */ case OP_scope_get_var_undef: case OP_scope_get_var: case OP_scope_put_var: case OP_scope_put_var_init: is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init); if (is_put) { if (s->closure_var[idx].is_lexical) { if (op == OP_scope_put_var_init) { /* 'this' can only be initialized once */ if (var_name == JS_ATOM_this) dbuf_putc(bc, OP_put_var_ref_check_init); else dbuf_putc(bc, OP_put_var_ref); } else { dbuf_putc(bc, OP_put_var_ref_check); } } else { dbuf_putc(bc, OP_put_var_ref); } } else { if (s->closure_var[idx].is_lexical) { dbuf_putc(bc, OP_get_var_ref_check); } else { dbuf_putc(bc, OP_get_var_ref); } } dbuf_put_u16(bc, idx); break; case OP_scope_delete_var: dbuf_putc(bc, OP_push_false); break; } goto done; } } /* global variable access */ switch (op) { case OP_scope_make_ref: if (label_done == -1 && can_opt_put_global_ref_value(bc_buf, ls->pos)) { pos_next = optimize_scope_make_global_ref(ctx, s, bc, bc_buf, ls, pos_next, var_name); } else { dbuf_putc(bc, OP_make_var_ref); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); } break; case OP_scope_get_ref: /* XXX: should create a dummy object with a named slot that is a reference to the global variable */ dbuf_putc(bc, OP_null); dbuf_putc(bc, OP_get_var); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); break; case OP_scope_get_var_undef: case OP_scope_get_var: case OP_scope_put_var: dbuf_putc(bc, OP_get_var_undef + (op - OP_scope_get_var_undef)); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); break; case OP_scope_put_var_init: dbuf_putc(bc, OP_put_var_init); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); break; case OP_scope_delete_var: dbuf_putc(bc, OP_delete_var); dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); break; } done: if (label_done >= 0) { dbuf_putc(bc, OP_label); dbuf_put_u32(bc, label_done); s->label_slots[label_done].pos2 = bc->size; } return pos_next; } static void mark_eval_captured_variables(JSContext *ctx, JSFunctionDef *s, int scope_level) { int idx; JSVarDef *vd; for (idx = s->scopes[scope_level].first; idx >= 0;) { vd = &s->vars[idx]; vd->is_captured = 1; idx = vd->scope_next; } } /* XXX: should handle the argument scope generically */ static BOOL is_var_in_arg_scope(const JSVarDef *vd) { return (vd->var_name == JS_ATOM_home_object || vd->var_name == JS_ATOM_this_active_func || vd->var_name == JS_ATOM_new_target || vd->var_name == JS_ATOM_this || vd->var_name == JS_ATOM__arg_var_ || vd->var_kind == JS_VAR_FUNCTION_NAME); } static void add_eval_variables(JSContext *ctx, JSFunctionDef *s) { JSFunctionDef *fd; JSVarDef *vd; int i, scope_level, scope_idx; BOOL has_this_binding, is_arg_scope; has_this_binding = s->has_this_binding; if (has_this_binding) { if (s->this_var_idx < 0) s->this_var_idx = add_var_this(ctx, s); if (s->new_target_var_idx < 0) s->new_target_var_idx = add_var(ctx, s, JS_ATOM_new_target); if (s->is_derived_class_constructor && s->this_active_func_var_idx < 0) s->this_active_func_var_idx = add_var(ctx, s, JS_ATOM_this_active_func); } if (s->is_func_expr && s->func_name != JS_ATOM_NULL) add_func_var(ctx, s, s->func_name); /* eval can use all the variables of the enclosing functions, so they must be all put in the closure. The closure variables are ordered by scope. It works only because no closure are created before. */ assert(s->is_eval || s->closure_var_count == 0); /* XXX: inefficient, but eval performance is less critical */ fd = s; for(;;) { scope_level = fd->parent_scope_level; fd = fd->parent; if (!fd) break; /* add 'this' if it was not previously added */ if (!has_this_binding && fd->has_this_binding) { if (fd->this_var_idx < 0) fd->this_var_idx = add_var_this(ctx, fd); if (fd->new_target_var_idx < 0) fd->new_target_var_idx = add_var(ctx, fd, JS_ATOM_new_target); if (fd->is_derived_class_constructor && fd->this_active_func_var_idx < 0) fd->this_active_func_var_idx = add_var(ctx, fd, JS_ATOM_this_active_func); has_this_binding = TRUE; } /* add function name */ if (fd->is_func_expr && fd->func_name != JS_ATOM_NULL) add_func_var(ctx, fd, fd->func_name); /* add lexical variables */ scope_idx = fd->scopes[scope_level].first; while (scope_idx >= 0) { vd = &fd->vars[scope_idx]; vd->is_captured = 1; get_closure_var(ctx, s, fd, FALSE, scope_idx, vd->var_name, vd->is_const, vd->is_lexical, vd->var_kind); scope_idx = vd->scope_next; } is_arg_scope = (scope_idx == ARG_SCOPE_END); if (!is_arg_scope) { /* add unscoped variables */ /* XXX: propagate is_const and var_kind too ? */ for(i = 0; i < fd->arg_count; i++) { vd = &fd->args[i]; if (vd->var_name != JS_ATOM_NULL) { get_closure_var(ctx, s, fd, TRUE, i, vd->var_name, FALSE, vd->is_lexical, JS_VAR_NORMAL); } } for(i = 0; i < fd->var_count; i++) { vd = &fd->vars[i]; /* do not close top level last result */ if (vd->scope_level == 0 && vd->var_name != JS_ATOM__ret_ && vd->var_name != JS_ATOM_NULL) { get_closure_var(ctx, s, fd, FALSE, i, vd->var_name, FALSE, vd->is_lexical, JS_VAR_NORMAL); } } } else { for(i = 0; i < fd->var_count; i++) { vd = &fd->vars[i]; /* do not close top level last result */ if (vd->scope_level == 0 && is_var_in_arg_scope(vd)) { get_closure_var(ctx, s, fd, FALSE, i, vd->var_name, FALSE, vd->is_lexical, JS_VAR_NORMAL); } } } if (fd->is_eval) { int idx; /* add direct eval variables (we are necessarily at the top level) */ for (idx = 0; idx < fd->closure_var_count; idx++) { JSClosureVar *cv = &fd->closure_var[idx]; get_closure_var2(ctx, s, fd, FALSE, cv->is_arg, idx, cv->var_name, cv->is_const, cv->is_lexical, cv->var_kind); } } } } static void set_closure_from_var(JSContext *ctx, JSClosureVar *cv, JSVarDef *vd, int var_idx) { cv->is_local = TRUE; cv->is_arg = FALSE; cv->is_const = vd->is_const; cv->is_lexical = vd->is_lexical; cv->var_kind = vd->var_kind; cv->var_idx = var_idx; cv->var_name = JS_DupAtom(ctx, vd->var_name); } /* for direct eval compilation: add references to the variables of the calling function */ static __exception int add_closure_variables(JSContext *ctx, JSFunctionDef *s, JSFunctionBytecode *b, int scope_idx) { int i, count; JSVarDef *vd; BOOL is_arg_scope; count = b->arg_count + b->var_count + b->closure_var_count; s->closure_var = NULL; s->closure_var_count = 0; s->closure_var_size = count; if (count == 0) return 0; s->closure_var = js_malloc(ctx, sizeof(s->closure_var[0]) * count); if (!s->closure_var) return -1; /* Add lexical variables in scope at the point of evaluation */ for (i = scope_idx; i >= 0;) { vd = &b->vardefs[b->arg_count + i]; if (vd->scope_level > 0) { JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; set_closure_from_var(ctx, cv, vd, i); } i = vd->scope_next; } is_arg_scope = (i == ARG_SCOPE_END); if (!is_arg_scope) { /* Add argument variables */ for(i = 0; i < b->arg_count; i++) { JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; vd = &b->vardefs[i]; cv->is_local = TRUE; cv->is_arg = TRUE; cv->is_const = FALSE; cv->is_lexical = FALSE; cv->var_kind = JS_VAR_NORMAL; cv->var_idx = i; cv->var_name = JS_DupAtom(ctx, vd->var_name); } /* Add local non lexical variables */ for(i = 0; i < b->var_count; i++) { vd = &b->vardefs[b->arg_count + i]; if (vd->scope_level == 0 && vd->var_name != JS_ATOM__ret_) { JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; set_closure_from_var(ctx, cv, vd, i); } } } else { /* only add pseudo variables */ for(i = 0; i < b->var_count; i++) { vd = &b->vardefs[b->arg_count + i]; if (vd->scope_level == 0 && is_var_in_arg_scope(vd)) { JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; set_closure_from_var(ctx, cv, vd, i); } } } for(i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv0 = &b->closure_var[i]; JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; cv->is_local = FALSE; cv->is_arg = cv0->is_arg; cv->is_const = cv0->is_const; cv->is_lexical = cv0->is_lexical; cv->var_kind = cv0->var_kind; cv->var_idx = i; cv->var_name = JS_DupAtom(ctx, cv0->var_name); } return 0; } typedef struct CodeContext { const uint8_t *bc_buf; /* code buffer */ int bc_len; /* length of the code buffer */ int pos; /* position past the matched code pattern */ int line_num; /* last visited OP_line_num parameter or -1 */ int op; int idx; int label; int val; JSAtom atom; } CodeContext; #define M2(op1, op2) ((op1) | ((op2) << 8)) #define M3(op1, op2, op3) ((op1) | ((op2) << 8) | ((op3) << 16)) #define M4(op1, op2, op3, op4) ((op1) | ((op2) << 8) | ((op3) << 16) | ((op4) << 24)) static BOOL code_match(CodeContext *s, int pos, ...) { const uint8_t *tab = s->bc_buf; int op, len, op1, line_num, pos_next; va_list ap; BOOL ret = FALSE; line_num = -1; va_start(ap, pos); for(;;) { op1 = va_arg(ap, int); if (op1 == -1) { s->pos = pos; s->line_num = line_num; ret = TRUE; break; } for (;;) { if (pos >= s->bc_len) goto done; op = tab[pos]; len = opcode_info[op].size; pos_next = pos + len; if (pos_next > s->bc_len) goto done; if (op == OP_line_num) { line_num = get_u32(tab + pos + 1); pos = pos_next; } else { break; } } if (op != op1) { if (op1 == (uint8_t)op1 || !op) break; if (op != (uint8_t)op1 && op != (uint8_t)(op1 >> 8) && op != (uint8_t)(op1 >> 16) && op != (uint8_t)(op1 >> 24)) { break; } s->op = op; } pos++; switch(opcode_info[op].fmt) { case OP_FMT_loc8: case OP_FMT_u8: { int idx = tab[pos]; int arg = va_arg(ap, int); if (arg == -1) { s->idx = idx; } else { if (arg != idx) goto done; } break; } case OP_FMT_u16: case OP_FMT_npop: case OP_FMT_loc: case OP_FMT_arg: case OP_FMT_var_ref: { int idx = get_u16(tab + pos); int arg = va_arg(ap, int); if (arg == -1) { s->idx = idx; } else { if (arg != idx) goto done; } break; } case OP_FMT_i32: case OP_FMT_u32: case OP_FMT_label: case OP_FMT_const: { s->label = get_u32(tab + pos); break; } case OP_FMT_label_u16: { s->label = get_u32(tab + pos); s->val = get_u16(tab + pos + 4); break; } case OP_FMT_atom: { s->atom = get_u32(tab + pos); break; } case OP_FMT_atom_u8: { s->atom = get_u32(tab + pos); s->val = get_u8(tab + pos + 4); break; } case OP_FMT_atom_u16: { s->atom = get_u32(tab + pos); s->val = get_u16(tab + pos + 4); break; } case OP_FMT_atom_label_u8: { s->atom = get_u32(tab + pos); s->label = get_u32(tab + pos + 4); s->val = get_u8(tab + pos + 8); break; } default: break; } pos = pos_next; } done: va_end(ap); return ret; } static void instantiate_hoisted_definitions(JSContext *ctx, JSFunctionDef *s, DynBuf *bc) { int i, idx; /* add the hoisted functions in arguments and local variables */ for(i = 0; i < s->arg_count; i++) { JSVarDef *vd = &s->args[i]; if (vd->func_pool_idx >= 0) { dbuf_putc(bc, OP_fclosure); dbuf_put_u32(bc, vd->func_pool_idx); dbuf_putc(bc, OP_put_arg); dbuf_put_u16(bc, i); } } for(i = 0; i < s->var_count; i++) { JSVarDef *vd = &s->vars[i]; if (vd->scope_level == 0 && vd->func_pool_idx >= 0) { dbuf_putc(bc, OP_fclosure); dbuf_put_u32(bc, vd->func_pool_idx); dbuf_putc(bc, OP_put_loc); dbuf_put_u16(bc, i); } } /* add the global variables (only happens if s->is_global_var is true) */ for(i = 0; i < s->global_var_count; i++) { JSGlobalVar *hf = &s->global_vars[i]; int has_closure = 0; BOOL force_init = hf->force_init; /* we are in an eval, so the closure contains all the enclosing variables */ /* If the outer function has a variable environment, create a property for the variable there */ for(idx = 0; idx < s->closure_var_count; idx++) { JSClosureVar *cv = &s->closure_var[idx]; if (cv->var_name == hf->var_name) { has_closure = 2; force_init = FALSE; break; } if (cv->var_name == JS_ATOM__var_ || cv->var_name == JS_ATOM__arg_var_) { dbuf_putc(bc, OP_get_var_ref); dbuf_put_u16(bc, idx); has_closure = 1; force_init = TRUE; break; } } if (!has_closure) { int flags; flags = 0; if (s->eval_type != JS_EVAL_TYPE_GLOBAL) flags |= JS_PROP_CONFIGURABLE; if (hf->cpool_idx >= 0 && !hf->is_lexical) { /* global function definitions need a specific handling */ dbuf_putc(bc, OP_fclosure); dbuf_put_u32(bc, hf->cpool_idx); dbuf_putc(bc, OP_define_func); dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); dbuf_putc(bc, flags); goto done_global_var; } else { if (hf->is_lexical) { flags |= DEFINE_GLOBAL_LEX_VAR; if (!hf->is_const) flags |= JS_PROP_WRITABLE; } dbuf_putc(bc, OP_define_var); dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); dbuf_putc(bc, flags); } } if (hf->cpool_idx >= 0 || force_init) { if (hf->cpool_idx >= 0) { dbuf_putc(bc, OP_fclosure); dbuf_put_u32(bc, hf->cpool_idx); if (hf->var_name == JS_ATOM__default_) { /* set default export function name */ dbuf_putc(bc, OP_set_name); dbuf_put_u32(bc, JS_DupAtom(ctx, JS_ATOM_default)); } } else { dbuf_putc(bc, OP_null); } if (has_closure == 2) { dbuf_putc(bc, OP_put_var_ref); dbuf_put_u16(bc, idx); } else if (has_closure == 1) { dbuf_putc(bc, OP_define_field); dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); dbuf_putc(bc, OP_drop); } else { /* XXX: Check if variable is writable and enumerable */ dbuf_putc(bc, OP_put_var); dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); } } done_global_var: JS_FreeAtom(ctx, hf->var_name); } js_free(ctx, s->global_vars); s->global_vars = NULL; s->global_var_count = 0; s->global_var_size = 0; } static int skip_dead_code(JSFunctionDef *s, const uint8_t *bc_buf, int bc_len, int pos, int *linep) { int op, len, label; for (; pos < bc_len; pos += len) { op = bc_buf[pos]; len = opcode_info[op].size; if (op == OP_line_num) { *linep = get_u32(bc_buf + pos + 1); } else if (op == OP_label) { label = get_u32(bc_buf + pos + 1); if (update_label(s, label, 0) > 0) break; assert(s->label_slots[label].first_reloc == NULL); } else { /* XXX: output a warning for unreachable code? */ JSAtom atom; switch(opcode_info[op].fmt) { case OP_FMT_label: case OP_FMT_label_u16: label = get_u32(bc_buf + pos + 1); update_label(s, label, -1); break; case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: label = get_u32(bc_buf + pos + 5); update_label(s, label, -1); /* fall thru */ case OP_FMT_atom: case OP_FMT_atom_u8: case OP_FMT_atom_u16: atom = get_u32(bc_buf + pos + 1); JS_FreeAtom(s->ctx, atom); break; default: break; } } } return pos; } static int get_label_pos(JSFunctionDef *s, int label) { int i, pos; for (i = 0; i < 20; i++) { pos = s->label_slots[label].pos; for (;;) { switch (s->byte_code.buf[pos]) { case OP_line_num: case OP_label: pos += 5; continue; case OP_goto: label = get_u32(s->byte_code.buf + pos + 1); break; default: return pos; } break; } } return pos; } /* convert global variable accesses to local variables or closure variables when necessary */ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s) { int pos, pos_next, bc_len, op, len, i, idx, line_num; uint8_t *bc_buf; JSAtom var_name; DynBuf bc_out; CodeContext cc; int scope; cc.bc_buf = bc_buf = s->byte_code.buf; cc.bc_len = bc_len = s->byte_code.size; js_dbuf_init(ctx, &bc_out); /* first pass for runtime checks (must be done before the variables are created) */ for(i = 0; i < s->global_var_count; i++) { JSGlobalVar *hf = &s->global_vars[i]; int flags; /* check if global variable (XXX: simplify) */ for(idx = 0; idx < s->closure_var_count; idx++) { JSClosureVar *cv = &s->closure_var[idx]; if (cv->var_name == hf->var_name) { if (s->eval_type == JS_EVAL_TYPE_DIRECT && cv->is_lexical) { /* Check if a lexical variable is redefined as 'var'. XXX: Could abort compilation here, but for consistency with the other checks, we delay the error generation. */ dbuf_putc(&bc_out, OP_throw_error); dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name)); dbuf_putc(&bc_out, JS_THROW_VAR_REDECL); } goto next; } if (cv->var_name == JS_ATOM__var_ || cv->var_name == JS_ATOM__arg_var_) goto next; } dbuf_putc(&bc_out, OP_check_define_var); dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name)); flags = 0; if (hf->is_lexical) flags |= DEFINE_GLOBAL_LEX_VAR; if (hf->cpool_idx >= 0) flags |= DEFINE_GLOBAL_FUNC_VAR; dbuf_putc(&bc_out, flags); next: ; } line_num = 0; /* avoid warning */ for (pos = 0; pos < bc_len; pos = pos_next) { op = bc_buf[pos]; len = opcode_info[op].size; pos_next = pos + len; switch(op) { case OP_line_num: line_num = get_u32(bc_buf + pos + 1); s->line_number_size++; goto no_change; case OP_eval: /* convert scope index to adjusted variable index */ { int call_argc = get_u16(bc_buf + pos + 1); scope = get_u16(bc_buf + pos + 1 + 2); mark_eval_captured_variables(ctx, s, scope); dbuf_putc(&bc_out, op); dbuf_put_u16(&bc_out, call_argc); dbuf_put_u16(&bc_out, s->scopes[scope].first - ARG_SCOPE_END); } break; case OP_apply_eval: /* convert scope index to adjusted variable index */ scope = get_u16(bc_buf + pos + 1); mark_eval_captured_variables(ctx, s, scope); dbuf_putc(&bc_out, op); dbuf_put_u16(&bc_out, s->scopes[scope].first - ARG_SCOPE_END); break; case OP_scope_get_var_checkthis: case OP_scope_get_var_undef: case OP_scope_get_var: case OP_scope_put_var: case OP_scope_delete_var: case OP_scope_get_ref: case OP_scope_put_var_init: var_name = get_u32(bc_buf + pos + 1); scope = get_u16(bc_buf + pos + 5); pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out, NULL, NULL, pos_next); JS_FreeAtom(ctx, var_name); break; case OP_scope_make_ref: { int label; LabelSlot *ls; var_name = get_u32(bc_buf + pos + 1); label = get_u32(bc_buf + pos + 5); scope = get_u16(bc_buf + pos + 9); ls = &s->label_slots[label]; ls->ref_count--; /* always remove label reference */ pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out, bc_buf, ls, pos_next); JS_FreeAtom(ctx, var_name); } break; case OP_gosub: s->jump_size++; if (OPTIMIZE) { /* remove calls to empty finalizers */ int label; LabelSlot *ls; label = get_u32(bc_buf + pos + 1); assert(label >= 0 && label < s->label_count); ls = &s->label_slots[label]; if (code_match(&cc, ls->pos, OP_ret, -1)) { ls->ref_count--; break; } } goto no_change; case OP_drop: if (0) { /* remove drops before return_undef */ /* do not perform this optimization in pass2 because it breaks patterns recognised in resolve_labels */ int pos1 = pos_next; int line1 = line_num; while (code_match(&cc, pos1, OP_drop, -1)) { if (cc.line_num >= 0) line1 = cc.line_num; pos1 = cc.pos; } if (code_match(&cc, pos1, OP_return_undef, -1)) { pos_next = pos1; if (line1 != -1 && line1 != line_num) { line_num = line1; s->line_number_size++; dbuf_putc(&bc_out, OP_line_num); dbuf_put_u32(&bc_out, line_num); } break; } } goto no_change; case OP_insert3: if (OPTIMIZE) { /* Transformation: insert3 put_array_el|put_ref_value drop -> put_array_el|put_ref_value */ if (code_match(&cc, pos_next, M2(OP_put_array_el, OP_put_ref_value), OP_drop, -1)) { dbuf_putc(&bc_out, cc.op); pos_next = cc.pos; if (cc.line_num != -1 && cc.line_num != line_num) { line_num = cc.line_num; s->line_number_size++; dbuf_putc(&bc_out, OP_line_num); dbuf_put_u32(&bc_out, line_num); } break; } } goto no_change; case OP_goto: s->jump_size++; /* fall thru */ case OP_tail_call: case OP_tail_call_method: case OP_return: case OP_return_undef: case OP_throw: case OP_throw_error: case OP_ret: if (OPTIMIZE) { /* remove dead code */ int line = -1; dbuf_put(&bc_out, bc_buf + pos, len); pos = skip_dead_code(s, bc_buf, bc_len, pos + len, &line); pos_next = pos; if (pos < bc_len && line >= 0 && line_num != line) { line_num = line; s->line_number_size++; dbuf_putc(&bc_out, OP_line_num); dbuf_put_u32(&bc_out, line_num); } break; } goto no_change; case OP_label: { int label; LabelSlot *ls; label = get_u32(bc_buf + pos + 1); assert(label >= 0 && label < s->label_count); ls = &s->label_slots[label]; ls->pos2 = bc_out.size + opcode_info[op].size; } goto no_change; case OP_enter_scope: { int scope_idx, scope = get_u16(bc_buf + pos + 1); if (scope == s->body_scope) { instantiate_hoisted_definitions(ctx, s, &bc_out); } for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) { JSVarDef *vd = &s->vars[scope_idx]; if (vd->scope_level != scope) break; if (vd->var_kind == JS_VAR_FUNCTION_DECL || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) { /* Initialize lexical variable upon entering scope */ dbuf_putc(&bc_out, OP_fclosure); dbuf_put_u32(&bc_out, vd->func_pool_idx); dbuf_putc(&bc_out, OP_put_loc); dbuf_put_u16(&bc_out, scope_idx); } else { /* XXX: should check if variable can be used before initialization */ dbuf_putc(&bc_out, OP_set_loc_uninitialized); dbuf_put_u16(&bc_out, scope_idx); } scope_idx = vd->scope_next; } } break; case OP_leave_scope: { int scope_idx, scope = get_u16(bc_buf + pos + 1); for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) { JSVarDef *vd = &s->vars[scope_idx]; if (vd->scope_level == scope) { if (vd->is_captured) { dbuf_putc(&bc_out, OP_close_loc); dbuf_put_u16(&bc_out, scope_idx); } scope_idx = vd->scope_next; } else { break; } } } break; case OP_set_name: { /* remove dummy set_name opcodes */ JSAtom name = get_u32(bc_buf + pos + 1); if (name == JS_ATOM_NULL) break; } goto no_change; case OP_if_false: case OP_if_true: case OP_catch: s->jump_size++; goto no_change; case OP_dup: if (OPTIMIZE) { /* Transformation: dup if_false(l1) drop, l1: if_false(l2) -> if_false(l2) */ /* Transformation: dup if_true(l1) drop, l1: if_true(l2) -> if_true(l2) */ if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), OP_drop, -1)) { int lab0, lab1, op1, pos1, line1, pos2; lab0 = lab1 = cc.label; assert(lab1 >= 0 && lab1 < s->label_count); op1 = cc.op; pos1 = cc.pos; line1 = cc.line_num; while (code_match(&cc, (pos2 = get_label_pos(s, lab1)), OP_dup, op1, OP_drop, -1)) { lab1 = cc.label; } if (code_match(&cc, pos2, op1, -1)) { s->jump_size++; update_label(s, lab0, -1); update_label(s, cc.label, +1); dbuf_putc(&bc_out, op1); dbuf_put_u32(&bc_out, cc.label); pos_next = pos1; if (line1 != -1 && line1 != line_num) { line_num = line1; s->line_number_size++; dbuf_putc(&bc_out, OP_line_num); dbuf_put_u32(&bc_out, line_num); } break; } } } goto no_change; case OP_nop: /* remove erased code */ break; case OP_set_class_name: /* only used during parsing */ break; case OP_get_field_opt_chain: /* equivalent to OP_get_field */ { JSAtom name = get_u32(bc_buf + pos + 1); dbuf_putc(&bc_out, OP_get_field); dbuf_put_u32(&bc_out, name); } break; case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */ dbuf_putc(&bc_out, OP_get_array_el); break; default: no_change: dbuf_put(&bc_out, bc_buf + pos, len); break; } } /* set the new byte code */ dbuf_free(&s->byte_code); s->byte_code = bc_out; if (dbuf_error(&s->byte_code)) { JS_ThrowOutOfMemory(ctx); return -1; } return 0; fail: /* continue the copy to keep the atom refcounts consistent */ /* XXX: find a better solution ? */ for (; pos < bc_len; pos = pos_next) { op = bc_buf[pos]; len = opcode_info[op].size; pos_next = pos + len; dbuf_put(&bc_out, bc_buf + pos, len); } dbuf_free(&s->byte_code); s->byte_code = bc_out; return -1; } /* the pc2line table gives a source position for each PC value */ static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, uint32_t source_pos) { if (s->line_number_slots != NULL && s->line_number_count < s->line_number_size && pc >= s->line_number_last_pc && source_pos != s->line_number_last) { s->line_number_slots[s->line_number_count].pc = pc; s->line_number_slots[s->line_number_count].source_pos = source_pos; s->line_number_count++; s->line_number_last_pc = pc; s->line_number_last = source_pos; } } /* XXX: could use a more compact storage */ /* XXX: get_line_col_cached() is slow. For more predictable performance, line/cols could be stored every N source bytes. Alternatively, get_line_col_cached() could be issued in emit_source_pos() so that the deltas are more likely to be small. */ static void compute_pc2line_info(JSFunctionDef *s) { if (!s->strip_debug) { int last_line_num, last_col_num; uint32_t last_pc = 0; int i, line_num, col_num; const uint8_t *buf_start = s->get_line_col_cache->buf_start; js_dbuf_init(s->ctx, &s->pc2line); last_line_num = get_line_col_cached(s->get_line_col_cache, &last_col_num, buf_start + s->source_pos); dbuf_put_leb128(&s->pc2line, last_line_num); /* line number minus 1 */ dbuf_put_leb128(&s->pc2line, last_col_num); /* column number minus 1 */ for (i = 0; i < s->line_number_count; i++) { uint32_t pc = s->line_number_slots[i].pc; uint32_t source_pos = s->line_number_slots[i].source_pos; int diff_pc, diff_line, diff_col; if (source_pos == -1) continue; diff_pc = pc - last_pc; if (diff_pc < 0) continue; line_num = get_line_col_cached(s->get_line_col_cache, &col_num, buf_start + source_pos); diff_line = line_num - last_line_num; diff_col = col_num - last_col_num; if (diff_line == 0 && diff_col == 0) continue; if (diff_line >= PC2LINE_BASE && diff_line < PC2LINE_BASE + PC2LINE_RANGE && diff_pc <= PC2LINE_DIFF_PC_MAX) { dbuf_putc(&s->pc2line, (diff_line - PC2LINE_BASE) + diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST); } else { /* longer encoding */ dbuf_putc(&s->pc2line, 0); dbuf_put_leb128(&s->pc2line, diff_pc); dbuf_put_sleb128(&s->pc2line, diff_line); } dbuf_put_sleb128(&s->pc2line, diff_col); last_pc = pc; last_line_num = line_num; last_col_num = col_num; } } } static RelocEntry *add_reloc(JSContext *ctx, LabelSlot *ls, uint32_t addr, int size) { RelocEntry *re; re = js_malloc(ctx, sizeof(*re)); if (!re) return NULL; re->addr = addr; re->size = size; re->next = ls->first_reloc; ls->first_reloc = re; return re; } static BOOL code_has_label(CodeContext *s, int pos, int label) { while (pos < s->bc_len) { int op = s->bc_buf[pos]; if (op == OP_line_num) { pos += 5; continue; } if (op == OP_label) { int lab = get_u32(s->bc_buf + pos + 1); if (lab == label) return TRUE; pos += 5; continue; } if (op == OP_goto) { int lab = get_u32(s->bc_buf + pos + 1); if (lab == label) return TRUE; } break; } return FALSE; } /* return the target label, following the OP_goto jumps the first opcode at destination is stored in *pop */ static int find_jump_target(JSFunctionDef *s, int label0, int *pop, int *pline) { int i, pos, op, label; label = label0; update_label(s, label, -1); for (i = 0; i < 10; i++) { assert(label >= 0 && label < s->label_count); pos = s->label_slots[label].pos2; for (;;) { switch(op = s->byte_code.buf[pos]) { case OP_line_num: if (pline) *pline = get_u32(s->byte_code.buf + pos + 1); /* fall thru */ case OP_label: pos += opcode_info[op].size; continue; case OP_goto: label = get_u32(s->byte_code.buf + pos + 1); break; case OP_drop: /* ignore drop opcodes if followed by OP_return_undef */ while (s->byte_code.buf[++pos] == OP_drop) continue; if (s->byte_code.buf[pos] == OP_return_undef) op = OP_return_undef; /* fall thru */ default: goto done; } break; } } /* cycle detected, could issue a warning */ /* XXX: the combination of find_jump_target() and skip_dead_code() seems incorrect with cyclic labels. See for exemple: for (;;) { l:break l; l:break l; l:break l; l:break l; } Avoiding changing the target is just a workaround and might not suffice to completely fix the problem. */ label = label0; done: *pop = op; update_label(s, label, +1); return label; } static void push_short_int(DynBuf *bc_out, int val) { #if SHORT_OPCODES if (val >= -1 && val <= 7) { dbuf_putc(bc_out, OP_push_0 + val); return; } if (val == (int8_t)val) { dbuf_putc(bc_out, OP_push_i8); dbuf_putc(bc_out, val); return; } if (val == (int16_t)val) { dbuf_putc(bc_out, OP_push_i16); dbuf_put_u16(bc_out, val); return; } #endif dbuf_putc(bc_out, OP_push_i32); dbuf_put_u32(bc_out, val); } static void put_short_code(DynBuf *bc_out, int op, int idx) { #if SHORT_OPCODES if (idx < 4) { switch (op) { case OP_get_loc: dbuf_putc(bc_out, OP_get_loc0 + idx); return; case OP_put_loc: dbuf_putc(bc_out, OP_put_loc0 + idx); return; case OP_set_loc: dbuf_putc(bc_out, OP_set_loc0 + idx); return; case OP_get_arg: dbuf_putc(bc_out, OP_get_arg0 + idx); return; case OP_put_arg: dbuf_putc(bc_out, OP_put_arg0 + idx); return; case OP_set_arg: dbuf_putc(bc_out, OP_set_arg0 + idx); return; case OP_get_var_ref: dbuf_putc(bc_out, OP_get_var_ref0 + idx); return; case OP_put_var_ref: dbuf_putc(bc_out, OP_put_var_ref0 + idx); return; case OP_set_var_ref: dbuf_putc(bc_out, OP_set_var_ref0 + idx); return; case OP_call: dbuf_putc(bc_out, OP_call0 + idx); return; } } if (idx < 256) { switch (op) { case OP_get_loc: dbuf_putc(bc_out, OP_get_loc8); dbuf_putc(bc_out, idx); return; case OP_put_loc: dbuf_putc(bc_out, OP_put_loc8); dbuf_putc(bc_out, idx); return; case OP_set_loc: dbuf_putc(bc_out, OP_set_loc8); dbuf_putc(bc_out, idx); return; } } #endif dbuf_putc(bc_out, op); dbuf_put_u16(bc_out, idx); } /* peephole optimizations and resolve goto/labels */ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) { int pos, pos_next, bc_len, op, op1, len, i, line_num; const uint8_t *bc_buf; DynBuf bc_out; LabelSlot *label_slots, *ls; RelocEntry *re, *re_next; CodeContext cc; int label; #if SHORT_OPCODES JumpSlot *jp; #endif label_slots = s->label_slots; line_num = s->source_pos; cc.bc_buf = bc_buf = s->byte_code.buf; cc.bc_len = bc_len = s->byte_code.size; js_dbuf_init(ctx, &bc_out); #if SHORT_OPCODES if (s->jump_size) { s->jump_slots = js_mallocz(s->ctx, sizeof(*s->jump_slots) * s->jump_size); if (s->jump_slots == NULL) return -1; } #endif /* XXX: Should skip this phase if not generating SHORT_OPCODES */ if (s->line_number_size && !s->strip_debug) { s->line_number_slots = js_mallocz(s->ctx, sizeof(*s->line_number_slots) * s->line_number_size); if (s->line_number_slots == NULL) return -1; s->line_number_last = s->source_pos; s->line_number_last_pc = 0; } /* initialize the 'this.active_func' variable if needed */ if (s->this_active_func_var_idx >= 0) { dbuf_putc(&bc_out, OP_special_object); dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); put_short_code(&bc_out, OP_put_loc, s->this_active_func_var_idx); } /* initialize the 'new.target' variable if needed */ if (s->new_target_var_idx >= 0) { dbuf_putc(&bc_out, OP_special_object); dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_NEW_TARGET); put_short_code(&bc_out, OP_put_loc, s->new_target_var_idx); } /* initialize the 'this' variable if needed. In a derived class constructor, this is initially uninitialized. */ if (s->this_var_idx >= 0) { if (s->is_derived_class_constructor) { dbuf_putc(&bc_out, OP_set_loc_uninitialized); dbuf_put_u16(&bc_out, s->this_var_idx); } else { dbuf_putc(&bc_out, OP_push_this); put_short_code(&bc_out, OP_put_loc, s->this_var_idx); } } /* initialize a reference to the current function if needed */ if (s->func_var_idx >= 0) { dbuf_putc(&bc_out, OP_special_object); dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); put_short_code(&bc_out, OP_put_loc, s->func_var_idx); } /* initialize the variable environment object if needed */ if (s->var_object_idx >= 0) { dbuf_putc(&bc_out, OP_special_object); dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); put_short_code(&bc_out, OP_put_loc, s->var_object_idx); } if (s->arg_var_object_idx >= 0) { dbuf_putc(&bc_out, OP_special_object); dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); put_short_code(&bc_out, OP_put_loc, s->arg_var_object_idx); } for (pos = 0; pos < bc_len; pos = pos_next) { int val; op = bc_buf[pos]; len = opcode_info[op].size; pos_next = pos + len; switch(op) { case OP_line_num: /* line number info (for debug). We put it in a separate compressed table to reduce memory usage and get better performance */ line_num = get_u32(bc_buf + pos + 1); break; case OP_label: { label = get_u32(bc_buf + pos + 1); assert(label >= 0 && label < s->label_count); ls = &label_slots[label]; assert(ls->addr == -1); ls->addr = bc_out.size; /* resolve the relocation entries */ for(re = ls->first_reloc; re != NULL; re = re_next) { int diff = ls->addr - re->addr; re_next = re->next; switch (re->size) { case 4: put_u32(bc_out.buf + re->addr, diff); break; case 2: assert(diff == (int16_t)diff); put_u16(bc_out.buf + re->addr, diff); break; case 1: assert(diff == (int8_t)diff); put_u8(bc_out.buf + re->addr, diff); break; } js_free(ctx, re); } ls->first_reloc = NULL; } break; case OP_call: case OP_call_method: { /* detect and transform tail calls */ int argc; argc = get_u16(bc_buf + pos + 1); if (code_match(&cc, pos_next, OP_return, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, op + 1, argc); pos_next = skip_dead_code(s, bc_buf, bc_len, cc.pos, &line_num); break; } add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, op, argc); break; } goto no_change; case OP_return: case OP_return_undef: case OP_throw: case OP_throw_error: pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, &line_num); goto no_change; case OP_goto: label = get_u32(bc_buf + pos + 1); has_goto: if (OPTIMIZE) { int line1 = -1; /* Use custom matcher because multiple labels can follow */ label = find_jump_target(s, label, &op1, &line1); if (code_has_label(&cc, pos_next, label)) { /* jump to next instruction: remove jump */ update_label(s, label, -1); break; } if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) { /* jump to return/throw: remove jump, append return/throw */ /* updating the line number obfuscates assembly listing */ //if (line1 != -1) line_num = line1; update_label(s, label, -1); add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, op1); pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, &line_num); break; } /* XXX: should duplicate single instructions followed by goto or return */ /* For example, can match one of these followed by return: push_i32 / push_const / push_atom_value / get_var / undefined / null / push_false / push_true / get_ref_value / get_loc / get_arg / get_var_ref */ } goto has_label; case OP_gosub: label = get_u32(bc_buf + pos + 1); if (0 && OPTIMIZE) { label = find_jump_target(s, label, &op1, NULL); if (op1 == OP_ret) { update_label(s, label, -1); /* empty finally clause: remove gosub */ break; } } goto has_label; case OP_catch: label = get_u32(bc_buf + pos + 1); goto has_label; case OP_if_true: case OP_if_false: label = get_u32(bc_buf + pos + 1); if (OPTIMIZE) { label = find_jump_target(s, label, &op1, NULL); /* transform if_false/if_true(l1) label(l1) -> drop label(l1) */ if (code_has_label(&cc, pos_next, label)) { update_label(s, label, -1); dbuf_putc(&bc_out, OP_drop); break; } /* transform if_false(l1) goto(l2) label(l1) -> if_false(l2) label(l1) */ if (code_match(&cc, pos_next, OP_goto, -1)) { int pos1 = cc.pos; int line1 = cc.line_num; if (code_has_label(&cc, pos1, label)) { if (line1 != -1) line_num = line1; pos_next = pos1; update_label(s, label, -1); label = cc.label; op ^= OP_if_true ^ OP_if_false; } } } has_label: add_pc2line_info(s, bc_out.size, line_num); if (op == OP_goto) { pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, &line_num); } assert(label >= 0 && label < s->label_count); ls = &label_slots[label]; #if SHORT_OPCODES jp = &s->jump_slots[s->jump_count++]; jp->op = op; jp->size = 4; jp->pos = bc_out.size + 1; jp->label = label; if (ls->addr == -1) { int diff = ls->pos2 - pos - 1; if (diff < 128 && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { jp->size = 1; jp->op = OP_if_false8 + (op - OP_if_false); dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false)); dbuf_putc(&bc_out, 0); if (!add_reloc(ctx, ls, bc_out.size - 1, 1)) goto fail; break; } if (diff < 32768 && op == OP_goto) { jp->size = 2; jp->op = OP_goto16; dbuf_putc(&bc_out, OP_goto16); dbuf_put_u16(&bc_out, 0); if (!add_reloc(ctx, ls, bc_out.size - 2, 2)) goto fail; break; } } else { int diff = ls->addr - bc_out.size - 1; if (diff == (int8_t)diff && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { jp->size = 1; jp->op = OP_if_false8 + (op - OP_if_false); dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false)); dbuf_putc(&bc_out, diff); break; } if (diff == (int16_t)diff && op == OP_goto) { jp->size = 2; jp->op = OP_goto16; dbuf_putc(&bc_out, OP_goto16); dbuf_put_u16(&bc_out, diff); break; } } #endif dbuf_putc(&bc_out, op); dbuf_put_u32(&bc_out, ls->addr - bc_out.size); if (ls->addr == -1) { /* unresolved yet: create a new relocation entry */ if (!add_reloc(ctx, ls, bc_out.size - 4, 4)) goto fail; } break; case OP_drop: if (OPTIMIZE) { /* remove useless drops before return */ if (code_match(&cc, pos_next, OP_return_undef, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; break; } } goto no_change; case OP_null: if (OPTIMIZE) { /* 1) remove null; drop → (nothing) */ if (code_match(&cc, pos_next, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; pos_next = cc.pos; break; } /* 2) null; return → return_undef */ if (code_match(&cc, pos_next, OP_return, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_return_undef); pos_next = cc.pos; break; } /* 3) null; if_false/if_true → goto or nop */ if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) { val = 0; goto has_constant_test; } #if SHORT_OPCODES /* 4a) null strict_eq → is_null */ if (code_match(&cc, pos_next, OP_strict_eq, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_is_null); pos_next = cc.pos; break; } /* 4b) null strict_neq; if_false/if_true → is_null + flipped branch */ if (code_match(&cc, pos_next, OP_strict_neq, M2(OP_if_false, OP_if_true), -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_is_null); pos_next = cc.pos; label = cc.label; op = cc.op ^ OP_if_false ^ OP_if_true; goto has_label; } #endif } /* didn’t match any of the above? fall straight into the OP_push_false / OP_push_true constant‐test code: */ case OP_push_false: case OP_push_true: if (OPTIMIZE) { val = (op == OP_push_true); if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) { has_constant_test: if (cc.line_num >= 0) line_num = cc.line_num; if (val == (cc.op - OP_if_false)) { /* e.g. null if_false(L) → goto L */ pos_next = cc.pos; op = OP_goto; label = cc.label; goto has_goto; } else { /* e.g. null if_true(L) → nop */ pos_next = cc.pos; update_label(s, cc.label, -1); break; } } } goto no_change; case OP_push_i32: if (OPTIMIZE) { /* transform i32(val) neg -> i32(-val) */ val = get_i32(bc_buf + pos + 1); if ((val != INT32_MIN && val != 0) && code_match(&cc, pos_next, OP_neg, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; if (code_match(&cc, cc.pos, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; } else { add_pc2line_info(s, bc_out.size, line_num); push_short_int(&bc_out, -val); } pos_next = cc.pos; break; } /* remove push/drop pairs generated by the parser */ if (code_match(&cc, pos_next, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; pos_next = cc.pos; break; } /* Optimize constant tests: `if (0)`, `if (1)`, `if (!0)`... */ if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) { val = (val != 0); goto has_constant_test; } add_pc2line_info(s, bc_out.size, line_num); push_short_int(&bc_out, val); break; } goto no_change; #if SHORT_OPCODES case OP_push_const: case OP_fclosure: if (OPTIMIZE) { int idx = get_u32(bc_buf + pos + 1); if (idx < 256) { add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_push_const8 + op - OP_push_const); dbuf_putc(&bc_out, idx); break; } } goto no_change; case OP_get_field: if (OPTIMIZE) { JSAtom atom = get_u32(bc_buf + pos + 1); if (atom == JS_ATOM_length) { JS_FreeAtom(ctx, atom); add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_get_length); break; } } goto no_change; #endif case OP_push_atom_value: if (OPTIMIZE) { JSAtom atom = get_u32(bc_buf + pos + 1); /* remove push/drop pairs generated by the parser */ if (code_match(&cc, pos_next, OP_drop, -1)) { JS_FreeAtom(ctx, atom); if (cc.line_num >= 0) line_num = cc.line_num; pos_next = cc.pos; break; } #if SHORT_OPCODES if (atom == JS_ATOM_empty_string) { JS_FreeAtom(ctx, atom); add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_push_empty_string); break; } #endif } goto no_change; case OP_to_propkey: if (OPTIMIZE) { /* remove redundant to_propkey opcodes when storing simple data */ if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_put_array_el, -1) || code_match(&cc, pos_next, M3(OP_push_i32, OP_push_const, OP_push_atom_value), OP_put_array_el, -1) || code_match(&cc, pos_next, M4(OP_null, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) { break; } } goto no_change; case OP_insert2: if (OPTIMIZE) { /* Transformation: insert2 put_field(a) drop -> put_field(a) insert2 put_var_strict(a) drop -> put_var_strict(a) */ if (code_match(&cc, pos_next, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, cc.op); dbuf_put_u32(&bc_out, cc.atom); pos_next = cc.pos; break; } } goto no_change; case OP_dup: if (OPTIMIZE) { /* Transformation: dup put_x(n) drop -> put_x(n) */ int op1, line2 = -1; /* Transformation: dup put_x(n) -> set_x(n) */ if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; op1 = cc.op + 1; /* put_x -> set_x */ pos_next = cc.pos; if (code_match(&cc, cc.pos, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; op1 -= 1; /* set_x drop -> put_x */ pos_next = cc.pos; if (code_match(&cc, cc.pos, op1 - 1, cc.idx, -1)) { line2 = cc.line_num; /* delay line number update */ op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ pos_next = cc.pos; } } add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, op1, cc.idx); if (line2 >= 0) line_num = line2; break; } } goto no_change; case OP_get_loc: if (OPTIMIZE) { /* transformation: get_loc(n) post_dec put_loc(n) drop -> dec_loc(n) get_loc(n) post_inc put_loc(n) drop -> inc_loc(n) get_loc(n) dec dup put_loc(n) drop -> dec_loc(n) get_loc(n) inc dup put_loc(n) drop -> inc_loc(n) */ int idx; idx = get_u16(bc_buf + pos + 1); if (idx >= 256) goto no_change; if (code_match(&cc, pos_next, M2(OP_post_dec, OP_post_inc), OP_put_loc, idx, OP_drop, -1) || code_match(&cc, pos_next, M2(OP_dec, OP_inc), OP_dup, OP_put_loc, idx, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, (cc.op == OP_inc || cc.op == OP_post_inc) ? OP_inc_loc : OP_dec_loc); dbuf_putc(&bc_out, idx); pos_next = cc.pos; break; } /* transformation: get_loc(n) push_atom_value(x) add dup put_loc(n) drop -> push_atom_value(x) add_loc(n) */ if (code_match(&cc, pos_next, OP_push_atom_value, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); #if SHORT_OPCODES if (cc.atom == JS_ATOM_empty_string) { JS_FreeAtom(ctx, cc.atom); dbuf_putc(&bc_out, OP_push_empty_string); } else #endif { dbuf_putc(&bc_out, OP_push_atom_value); dbuf_put_u32(&bc_out, cc.atom); } dbuf_putc(&bc_out, OP_add_loc); dbuf_putc(&bc_out, idx); pos_next = cc.pos; break; } /* transformation: get_loc(n) push_i32(x) add dup put_loc(n) drop -> push_i32(x) add_loc(n) */ if (code_match(&cc, pos_next, OP_push_i32, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); push_short_int(&bc_out, cc.label); dbuf_putc(&bc_out, OP_add_loc); dbuf_putc(&bc_out, idx); pos_next = cc.pos; break; } /* transformation: XXX: also do these: get_loc(n) get_loc(x) add dup put_loc(n) drop -> get_loc(x) add_loc(n) get_loc(n) get_arg(x) add dup put_loc(n) drop -> get_arg(x) add_loc(n) get_loc(n) get_var_ref(x) add dup put_loc(n) drop -> get_var_ref(x) add_loc(n) */ if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, cc.op, cc.idx); dbuf_putc(&bc_out, OP_add_loc); dbuf_putc(&bc_out, idx); pos_next = cc.pos; break; } add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, op, idx); break; } goto no_change; #if SHORT_OPCODES case OP_get_arg: case OP_get_var_ref: if (OPTIMIZE) { int idx; idx = get_u16(bc_buf + pos + 1); add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, op, idx); break; } goto no_change; #endif case OP_put_loc: case OP_put_arg: case OP_put_var_ref: if (OPTIMIZE) { /* transformation: put_x(n) get_x(n) -> set_x(n) */ int idx; idx = get_u16(bc_buf + pos + 1); if (code_match(&cc, pos_next, op - 1, idx, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, op + 1, idx); pos_next = cc.pos; break; } add_pc2line_info(s, bc_out.size, line_num); put_short_code(&bc_out, op, idx); break; } goto no_change; case OP_post_inc: case OP_post_dec: if (OPTIMIZE) { /* transformation: post_inc put_x drop -> inc put_x post_inc perm3 put_field drop -> inc put_field post_inc perm3 put_var_strict drop -> inc put_var_strict post_inc perm4 put_array_el drop -> inc put_array_el */ int op1, idx; if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; op1 = cc.op; idx = cc.idx; pos_next = cc.pos; if (code_match(&cc, cc.pos, op1 - 1, idx, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ pos_next = cc.pos; } add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec)); put_short_code(&bc_out, op1, idx); break; } if (code_match(&cc, pos_next, OP_perm3, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec)); dbuf_putc(&bc_out, cc.op); dbuf_put_u32(&bc_out, cc.atom); pos_next = cc.pos; break; } if (code_match(&cc, pos_next, OP_perm4, OP_put_array_el, OP_drop, -1)) { if (cc.line_num >= 0) line_num = cc.line_num; add_pc2line_info(s, bc_out.size, line_num); dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec)); dbuf_putc(&bc_out, OP_put_array_el); pos_next = cc.pos; break; } } goto no_change; default: no_change: add_pc2line_info(s, bc_out.size, line_num); dbuf_put(&bc_out, bc_buf + pos, len); break; } } /* check that there were no missing labels */ for(i = 0; i < s->label_count; i++) { assert(label_slots[i].first_reloc == NULL); } #if SHORT_OPCODES if (OPTIMIZE) { /* more jump optimizations */ int patch_offsets = 0; for (i = 0, jp = s->jump_slots; i < s->jump_count; i++, jp++) { LabelSlot *ls; JumpSlot *jp1; int j, pos, diff, delta; delta = 3; switch (op = jp->op) { case OP_goto16: delta = 1; /* fall thru */ case OP_if_false: case OP_if_true: case OP_goto: pos = jp->pos; diff = s->label_slots[jp->label].addr - pos; if (diff >= -128 && diff <= 127 + delta) { //put_u8(bc_out.buf + pos, diff); jp->size = 1; if (op == OP_goto16) { bc_out.buf[pos - 1] = jp->op = OP_goto8; } else { bc_out.buf[pos - 1] = jp->op = OP_if_false8 + (op - OP_if_false); } goto shrink; } else if (diff == (int16_t)diff && op == OP_goto) { //put_u16(bc_out.buf + pos, diff); jp->size = 2; delta = 2; bc_out.buf[pos - 1] = jp->op = OP_goto16; shrink: /* XXX: should reduce complexity, using 2 finger copy scheme */ memmove(bc_out.buf + pos + jp->size, bc_out.buf + pos + jp->size + delta, bc_out.size - pos - jp->size - delta); bc_out.size -= delta; patch_offsets++; for (j = 0, ls = s->label_slots; j < s->label_count; j++, ls++) { if (ls->addr > pos) ls->addr -= delta; } for (j = i + 1, jp1 = jp + 1; j < s->jump_count; j++, jp1++) { if (jp1->pos > pos) jp1->pos -= delta; } for (j = 0; j < s->line_number_count; j++) { if (s->line_number_slots[j].pc > pos) s->line_number_slots[j].pc -= delta; } continue; } break; } } if (patch_offsets) { JumpSlot *jp1; int j; for (j = 0, jp1 = s->jump_slots; j < s->jump_count; j++, jp1++) { int diff1 = s->label_slots[jp1->label].addr - jp1->pos; switch (jp1->size) { case 1: put_u8(bc_out.buf + jp1->pos, diff1); break; case 2: put_u16(bc_out.buf + jp1->pos, diff1); break; case 4: put_u32(bc_out.buf + jp1->pos, diff1); break; } } } } js_free(ctx, s->jump_slots); s->jump_slots = NULL; #endif js_free(ctx, s->label_slots); s->label_slots = NULL; /* XXX: should delay until copying to runtime bytecode function */ compute_pc2line_info(s); js_free(ctx, s->line_number_slots); s->line_number_slots = NULL; /* set the new byte code */ dbuf_free(&s->byte_code); s->byte_code = bc_out; s->use_short_opcodes = TRUE; if (dbuf_error(&s->byte_code)) { JS_ThrowOutOfMemory(ctx); return -1; } return 0; fail: /* XXX: not safe */ dbuf_free(&bc_out); return -1; } /* compute the maximum stack size needed by the function */ typedef struct StackSizeState { int bc_len; int stack_len_max; uint16_t *stack_level_tab; int32_t *catch_pos_tab; int *pc_stack; int pc_stack_len; int pc_stack_size; } StackSizeState; /* 'op' is only used for error indication */ static __exception int ss_check(JSContext *ctx, StackSizeState *s, int pos, int op, int stack_len, int catch_pos) { if ((unsigned)pos >= s->bc_len) { JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); return -1; } if (stack_len > s->stack_len_max) { s->stack_len_max = stack_len; if (s->stack_len_max > JS_STACK_SIZE_MAX) { JS_ThrowInternalError(ctx, "stack overflow (op=%d, pc=%d)", op, pos); return -1; } } if (s->stack_level_tab[pos] != 0xffff) { /* already explored: check that the stack size is consistent */ if (s->stack_level_tab[pos] != stack_len) { JS_ThrowInternalError(ctx, "inconsistent stack size: %d %d (pc=%d)", s->stack_level_tab[pos], stack_len, pos); return -1; } else if (s->catch_pos_tab[pos] != catch_pos) { JS_ThrowInternalError(ctx, "inconsistent catch position: %d %d (pc=%d)", s->catch_pos_tab[pos], catch_pos, pos); return -1; } else { return 0; } } /* mark as explored and store the stack size */ s->stack_level_tab[pos] = stack_len; s->catch_pos_tab[pos] = catch_pos; /* queue the new PC to explore */ if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack[0]), &s->pc_stack_size, s->pc_stack_len + 1)) return -1; s->pc_stack[s->pc_stack_len++] = pos; return 0; } static __exception int compute_stack_size(JSContext *ctx, JSFunctionDef *fd, int *pstack_size) { StackSizeState s_s, *s = &s_s; int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level; const JSOpCode *oi; const uint8_t *bc_buf; bc_buf = fd->byte_code.buf; s->bc_len = fd->byte_code.size; /* bc_len > 0 */ s->stack_level_tab = js_malloc(ctx, sizeof(s->stack_level_tab[0]) * s->bc_len); if (!s->stack_level_tab) return -1; for(i = 0; i < s->bc_len; i++) s->stack_level_tab[i] = 0xffff; s->pc_stack = NULL; s->catch_pos_tab = js_malloc(ctx, sizeof(s->catch_pos_tab[0]) * s->bc_len); if (!s->catch_pos_tab) goto fail; s->stack_len_max = 0; s->pc_stack_len = 0; s->pc_stack_size = 0; /* breadth-first graph exploration */ if (ss_check(ctx, s, 0, OP_invalid, 0, -1)) goto fail; while (s->pc_stack_len > 0) { pos = s->pc_stack[--s->pc_stack_len]; stack_len = s->stack_level_tab[pos]; catch_pos = s->catch_pos_tab[pos]; op = bc_buf[pos]; if (op == 0 || op >= OP_COUNT) { JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos); goto fail; } oi = &short_opcode_info(op); #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64) printf("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos); #endif pos_next = pos + oi->size; if (pos_next > s->bc_len) { JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); goto fail; } n_pop = oi->n_pop; /* call pops a variable number of arguments */ if (oi->fmt == OP_FMT_npop || oi->fmt == OP_FMT_npop_u16) { n_pop += get_u16(bc_buf + pos + 1); } else { #if SHORT_OPCODES if (oi->fmt == OP_FMT_npopx) { n_pop += op - OP_call0; } #endif } if (stack_len < n_pop) { JS_ThrowInternalError(ctx, "stack underflow (op=%d, pc=%d)", op, pos); goto fail; } stack_len += oi->n_push - n_pop; if (stack_len > s->stack_len_max) { s->stack_len_max = stack_len; if (s->stack_len_max > JS_STACK_SIZE_MAX) { JS_ThrowInternalError(ctx, "stack overflow (op=%d, pc=%d)", op, pos); goto fail; } } switch(op) { case OP_tail_call: case OP_tail_call_method: case OP_return: case OP_return_undef: case OP_throw: case OP_throw_error: case OP_ret: goto done_insn; case OP_goto: diff = get_u32(bc_buf + pos + 1); pos_next = pos + 1 + diff; break; #if SHORT_OPCODES case OP_goto16: diff = (int16_t)get_u16(bc_buf + pos + 1); pos_next = pos + 1 + diff; break; case OP_goto8: diff = (int8_t)bc_buf[pos + 1]; pos_next = pos + 1 + diff; break; case OP_if_true8: case OP_if_false8: diff = (int8_t)bc_buf[pos + 1]; if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) goto fail; break; #endif case OP_if_true: case OP_if_false: diff = get_u32(bc_buf + pos + 1); if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) goto fail; break; case OP_gosub: diff = get_u32(bc_buf + pos + 1); if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos)) goto fail; break; case OP_catch: diff = get_u32(bc_buf + pos + 1); if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) goto fail; catch_pos = pos; break; /* we assume the catch offset entry is only removed with some op codes */ case OP_drop: catch_level = stack_len; goto check_catch; case OP_nip: catch_level = stack_len - 1; goto check_catch; case OP_nip1: catch_level = stack_len - 1; check_catch: if (catch_pos >= 0) { int level; level = s->stack_level_tab[catch_pos]; /* catch_level = stack_level before op_catch is executed ? */ if (catch_level == level) { catch_pos = s->catch_pos_tab[catch_pos]; } } break; case OP_nip_catch: if (catch_pos < 0) { JS_ThrowInternalError(ctx, "nip_catch: no catch op (pc=%d)", pos); goto fail; } stack_len = s->stack_level_tab[catch_pos]; stack_len++; /* no stack overflow is possible by construction */ catch_pos = s->catch_pos_tab[catch_pos]; break; default: break; } if (ss_check(ctx, s, pos_next, op, stack_len, catch_pos)) goto fail; done_insn: ; } js_free(ctx, s->pc_stack); js_free(ctx, s->catch_pos_tab); js_free(ctx, s->stack_level_tab); *pstack_size = s->stack_len_max; return 0; fail: js_free(ctx, s->pc_stack); js_free(ctx, s->catch_pos_tab); js_free(ctx, s->stack_level_tab); *pstack_size = 0; return -1; } /* create a function object from a function definition. The function definition is freed. All the child functions are also created. It must be done this way to resolve all the variables. */ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) { JSValue func_obj; JSFunctionBytecode *b; struct list_head *el, *el1; int stack_size, scope, idx; int function_size, byte_code_offset, cpool_offset; int closure_var_offset, vardefs_offset; /* recompute scope linkage */ for (scope = 0; scope < fd->scope_count; scope++) { fd->scopes[scope].first = -1; } if (fd->has_parameter_expressions) { /* special end of variable list marker for the argument scope */ fd->scopes[ARG_SCOPE_INDEX].first = ARG_SCOPE_END; } for (idx = 0; idx < fd->var_count; idx++) { JSVarDef *vd = &fd->vars[idx]; vd->scope_next = fd->scopes[vd->scope_level].first; fd->scopes[vd->scope_level].first = idx; } for (scope = 2; scope < fd->scope_count; scope++) { JSVarScope *sd = &fd->scopes[scope]; if (sd->first < 0) sd->first = fd->scopes[sd->parent].first; } for (idx = 0; idx < fd->var_count; idx++) { JSVarDef *vd = &fd->vars[idx]; if (vd->scope_next < 0 && vd->scope_level > 1) { scope = fd->scopes[vd->scope_level].parent; vd->scope_next = fd->scopes[scope].first; } } /* if the function contains an eval call, the closure variables are used to compile the eval and they must be ordered by scope, so it is necessary to create the closure variables before any other variable lookup is done. */ if (fd->has_eval_call) add_eval_variables(ctx, fd); /* first create all the child functions */ list_for_each_safe(el, el1, &fd->child_list) { JSFunctionDef *fd1; int cpool_idx; fd1 = list_entry(el, JSFunctionDef, link); cpool_idx = fd1->parent_cpool_idx; func_obj = js_create_function(ctx, fd1); if (JS_IsException(func_obj)) goto fail; /* save it in the constant pool */ assert(cpool_idx >= 0); fd->cpool[cpool_idx] = func_obj; } #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4) if (!fd->strip_debug) { printf("pass 1\n"); dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); printf("\n"); } #endif if (resolve_variables(ctx, fd)) goto fail; #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2) if (!fd->strip_debug) { printf("pass 2\n"); dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); printf("\n"); } #endif if (resolve_labels(ctx, fd)) goto fail; if (compute_stack_size(ctx, fd, &stack_size) < 0) goto fail; if (fd->strip_debug) { function_size = offsetof(JSFunctionBytecode, debug); } else { function_size = sizeof(*b); } cpool_offset = function_size; function_size += fd->cpool_count * sizeof(*fd->cpool); vardefs_offset = function_size; if (!fd->strip_debug || fd->has_eval_call) { function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs); } closure_var_offset = function_size; function_size += fd->closure_var_count * sizeof(*fd->closure_var); byte_code_offset = function_size; function_size += fd->byte_code.size; b = js_mallocz(ctx, function_size); if (!b) goto fail; b->header.ref_count = 1; b->byte_code_buf = (void *)((uint8_t*)b + byte_code_offset); b->byte_code_len = fd->byte_code.size; memcpy(b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size); js_free(ctx, fd->byte_code.buf); fd->byte_code.buf = NULL; b->func_name = fd->func_name; if (fd->arg_count + fd->var_count > 0) { if (fd->strip_debug && !fd->has_eval_call) { /* Strip variable definitions not needed at runtime */ int i; for(i = 0; i < fd->var_count; i++) { JS_FreeAtom(ctx, fd->vars[i].var_name); } for(i = 0; i < fd->arg_count; i++) { JS_FreeAtom(ctx, fd->args[i].var_name); } for(i = 0; i < fd->closure_var_count; i++) { JS_FreeAtom(ctx, fd->closure_var[i].var_name); fd->closure_var[i].var_name = JS_ATOM_NULL; } } else { b->vardefs = (void *)((uint8_t*)b + vardefs_offset); memcpy_no_ub(b->vardefs, fd->args, fd->arg_count * sizeof(fd->args[0])); memcpy_no_ub(b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof(fd->vars[0])); } b->var_count = fd->var_count; b->arg_count = fd->arg_count; b->defined_arg_count = fd->defined_arg_count; js_free(ctx, fd->args); js_free(ctx, fd->vars); } b->cpool_count = fd->cpool_count; if (b->cpool_count) { b->cpool = (void *)((uint8_t*)b + cpool_offset); memcpy(b->cpool, fd->cpool, b->cpool_count * sizeof(*b->cpool)); } js_free(ctx, fd->cpool); fd->cpool = NULL; b->stack_size = stack_size; if (fd->strip_debug) { JS_FreeAtom(ctx, fd->filename); dbuf_free(&fd->pc2line); // probably useless } else { /* XXX: source and pc2line info should be packed at the end of the JSFunctionBytecode structure, avoiding allocation overhead */ b->has_debug = 1; b->debug.filename = fd->filename; //DynBuf pc2line; //compute_pc2line_info(fd, &pc2line); //js_free(ctx, fd->line_number_slots) b->debug.pc2line_buf = js_realloc(ctx, fd->pc2line.buf, fd->pc2line.size); if (!b->debug.pc2line_buf) b->debug.pc2line_buf = fd->pc2line.buf; b->debug.pc2line_len = fd->pc2line.size; b->debug.source = fd->source; b->debug.source_len = fd->source_len; } if (fd->scopes != fd->def_scope_array) js_free(ctx, fd->scopes); b->closure_var_count = fd->closure_var_count; if (b->closure_var_count) { b->closure_var = (void *)((uint8_t*)b + closure_var_offset); memcpy(b->closure_var, fd->closure_var, b->closure_var_count * sizeof(*b->closure_var)); } js_free(ctx, fd->closure_var); fd->closure_var = NULL; b->has_prototype = fd->has_prototype; b->has_simple_parameter_list = fd->has_simple_parameter_list; b->js_mode = fd->js_mode; b->is_derived_class_constructor = fd->is_derived_class_constructor; #ifdef DUMP_PROFILE /* Initialize profiling data */ memset(&b->profile, 0, sizeof(b->profile)); b->call_sites = NULL; b->call_site_count = 0; b->call_site_capacity = 0; b->prop_sites = NULL; b->prop_site_count = 0; b->prop_site_capacity = 0; #endif /* Initialize IC slots (allocate lazily based on bytecode analysis) */ b->ic_slots = NULL; b->ic_count = 0; b->new_target_allowed = fd->new_target_allowed; b->is_direct_or_indirect_eval = (fd->eval_type == JS_EVAL_TYPE_DIRECT || fd->eval_type == JS_EVAL_TYPE_INDIRECT); b->realm = JS_DupContext(ctx); add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); #if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1) if (!fd->strip_debug) { js_dump_function_bytecode(ctx, b); } #endif if (fd->parent) { /* remove from parent list */ list_del(&fd->link); } js_free(ctx, fd); return JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); fail: js_free_function_def(ctx, fd); return JS_EXCEPTION; } /* IC helper functions */ static ICSlot *ic_get_slot(JSFunctionBytecode *b, uint32_t ic_index) { if (ic_index >= b->ic_count) return NULL; return &b->ic_slots[ic_index]; } static void ic_init_call(ICSlot *slot) { memset(slot, 0, sizeof(*slot)); slot->kind = IC_CALL; slot->state = IC_STATE_UNINIT; } static void ic_init_get_prop(ICSlot *slot) { memset(slot, 0, sizeof(*slot)); slot->kind = IC_GET_PROP; slot->state = IC_STATE_UNINIT; } static void ic_init_set_prop(ICSlot *slot) { memset(slot, 0, sizeof(*slot)); slot->kind = IC_SET_PROP; slot->state = IC_STATE_UNINIT; } static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b) { int i; free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE); if (b->vardefs) { for(i = 0; i < b->arg_count + b->var_count; i++) { JS_FreeAtomRT(rt, b->vardefs[i].var_name); } } for(i = 0; i < b->cpool_count; i++) JS_FreeValueRT(rt, b->cpool[i]); for(i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; JS_FreeAtomRT(rt, cv->var_name); } if (b->realm) JS_FreeContext(b->realm); JS_FreeAtomRT(rt, b->func_name); if (b->has_debug) { JS_FreeAtomRT(rt, b->debug.filename); js_free_rt(rt, b->debug.pc2line_buf); js_free_rt(rt, b->debug.source); } #ifdef DUMP_PROFILE /* Free profiling data */ js_free_rt(rt, b->call_sites); js_free_rt(rt, b->prop_sites); #endif /* Free IC slots */ if (b->ic_slots) { js_free_rt(rt, b->ic_slots); } remove_gc_object(&b->header); if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && b->header.ref_count != 0) { list_add_tail(&b->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, b); } } static __exception int js_parse_directives(JSParseState *s) { char str[20]; JSParsePos pos; BOOL has_semi; if (s->token.val != TOK_STRING) return 0; js_parse_get_pos(s, &pos); while(s->token.val == TOK_STRING) { /* Copy actual source string representation */ snprintf(str, sizeof str, "%.*s", (int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1); if (next_token(s)) return -1; has_semi = FALSE; switch (s->token.val) { case ';': if (next_token(s)) return -1; has_semi = TRUE; break; case '}': case TOK_EOF: has_semi = TRUE; break; case TOK_NUMBER: case TOK_STRING: case TOK_TEMPLATE: case TOK_IDENT: case TOK_REGEXP: case TOK_DEC: case TOK_INC: case TOK_NULL: case TOK_FALSE: case TOK_TRUE: case TOK_IF: case TOK_RETURN: case TOK_VAR: case TOK_THIS: case TOK_DELETE: case TOK_NEW: case TOK_DO: case TOK_WHILE: case TOK_FOR: case TOK_SWITCH: case TOK_THROW: case TOK_TRY: case TOK_FUNCTION: case TOK_DEBUGGER: case TOK_DEF: case TOK_ENUM: case TOK_EXPORT: case TOK_IMPORT: case TOK_INTERFACE: /* automatic insertion of ';' */ if (s->got_lf) has_semi = TRUE; break; default: break; } if (!has_semi) break; } return js_parse_seek_token(s, &pos); } /* return TRUE if the keyword is forbidden only in strict mode */ static BOOL is_strict_future_keyword(JSAtom atom) { return (atom >= JS_ATOM_LAST_KEYWORD + 1 && atom <= JS_ATOM_LAST_STRICT_KEYWORD); } static int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd, JSAtom func_name) { JSAtom name; int i, idx; if (!fd->has_simple_parameter_list && fd->has_use_strict) { return js_parse_error(s, "\"use strict\" not allowed in function with default or destructuring parameter"); } if (func_name == JS_ATOM_eval || is_strict_future_keyword(func_name)) { return js_parse_error(s, "invalid function name in strict code"); } for (idx = 0; idx < fd->arg_count; idx++) { name = fd->args[idx].var_name; if (name == JS_ATOM_eval || is_strict_future_keyword(name)) { return js_parse_error(s, "invalid argument name in strict code"); } } for (idx = 0; idx < fd->arg_count; idx++) { name = fd->args[idx].var_name; if (name != JS_ATOM_NULL) { for (i = 0; i < idx; i++) { if (fd->args[i].var_name == name) goto duplicate; } /* Check if argument name duplicates a destructuring parameter */ /* XXX: should have a flag for such variables */ for (i = 0; i < fd->var_count; i++) { if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0) goto duplicate; } } } return 0; duplicate: return js_parse_error(s, "duplicate argument names not allowed in this context"); } /* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */ static __exception int js_parse_function_decl2(JSParseState *s, JSParseFunctionEnum func_type, JSAtom func_name, const uint8_t *ptr, JSFunctionDef **pfd) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; BOOL is_expr; int func_idx, lexical_func_idx = -1; BOOL has_opt_arg; BOOL create_func_var = FALSE; is_expr = (func_type != JS_PARSE_FUNC_STATEMENT && func_type != JS_PARSE_FUNC_VAR); if (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR || func_type == JS_PARSE_FUNC_EXPR) { if (next_token(s)) return -1; if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) return js_parse_error_reserved_identifier(s); func_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) { JS_FreeAtom(ctx, func_name); return -1; } } else { if (func_type != JS_PARSE_FUNC_EXPR) return js_parse_error(s, "function name expected"); } } else if (func_type != JS_PARSE_FUNC_ARROW) { func_name = JS_DupAtom(ctx, func_name); } if (func_type == JS_PARSE_FUNC_VAR) { /* Create the lexical name here so that the function closure contains it */ if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_GLOBAL && fd->scope_level == fd->body_scope) { /* avoid creating a lexical variable in the global scope. XXX: check annex B */ JSGlobalVar *hf; hf = find_global_var(fd, func_name); /* XXX: should check scope chain */ if (hf && hf->scope_level == fd->scope_level) { js_parse_error(s, "invalid redefinition of global identifier"); JS_FreeAtom(ctx, func_name); return -1; } } else { /* Always create a lexical name, fail if at the same scope as existing name */ /* Lexical variable will be initialized upon entering scope */ lexical_func_idx = define_var(s, fd, func_name, JS_VAR_DEF_FUNCTION_DECL); if (lexical_func_idx < 0) { JS_FreeAtom(ctx, func_name); return -1; } } } fd = js_new_function_def(ctx, fd, FALSE, is_expr, s->filename, ptr, &s->get_line_col_cache); if (!fd) { JS_FreeAtom(ctx, func_name); return -1; } if (pfd) *pfd = fd; s->cur_func = fd; fd->func_name = func_name; /* XXX: test !fd->is_generator is always false */ fd->has_prototype = (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR || func_type == JS_PARSE_FUNC_EXPR); fd->has_this_binding = (func_type != JS_PARSE_FUNC_ARROW && func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT); fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); if (func_type == JS_PARSE_FUNC_ARROW) { fd->new_target_allowed = fd->parent->new_target_allowed; } else if (func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) { fd->new_target_allowed = TRUE; // although new.target === undefined } else { fd->new_target_allowed = TRUE; } /* fd->in_function_body == FALSE prevents yield/await during the parsing of the arguments in generator/async functions. They are parsed as regular identifiers for other function kinds. */ fd->func_type = func_type; /* parse arguments */ fd->has_simple_parameter_list = TRUE; fd->has_parameter_expressions = FALSE; has_opt_arg = FALSE; if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) { JSAtom name; if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); goto fail; } name = s->token.u.ident.atom; if (add_arg(ctx, fd, name) < 0) goto fail; fd->defined_arg_count = 1; } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { if (s->token.val == '(') { int skip_bits; /* if there is an '=' inside the parameter list, we consider there is a parameter expression inside */ js_parse_skip_parens_token(s, &skip_bits, FALSE); if (skip_bits & SKIP_HAS_ASSIGNMENT) fd->has_parameter_expressions = TRUE; if (next_token(s)) goto fail; } else { if (js_parse_expect(s, '(')) goto fail; } if (fd->has_parameter_expressions) { fd->scope_level = -1; /* force no parent scope */ if (push_scope(s) < 0) return -1; } while (s->token.val != ')') { JSAtom name; BOOL rest = FALSE; int idx, has_initializer; if (s->token.val == TOK_ELLIPSIS) { if (func_type == JS_PARSE_FUNC_SETTER) goto fail_accessor; fd->has_simple_parameter_list = FALSE; rest = TRUE; if (next_token(s)) goto fail; } if (s->token.val == '[' || s->token.val == '{') { fd->has_simple_parameter_list = FALSE; if (rest) { emit_op(s, OP_rest); emit_u16(s, fd->arg_count); } else { /* unnamed arg for destructuring */ idx = add_arg(ctx, fd, JS_ATOM_NULL); emit_op(s, OP_get_arg); emit_u16(s, idx); } has_initializer = js_parse_destructuring_element(s, TOK_VAR, 1, TRUE, -1, TRUE, FALSE); if (has_initializer < 0) goto fail; if (has_initializer) has_opt_arg = TRUE; if (!has_opt_arg) fd->defined_arg_count++; } else if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); goto fail; } name = s->token.u.ident.atom; if (fd->has_parameter_expressions) { if (js_parse_check_duplicate_parameter(s, name)) goto fail; if (define_var(s, fd, name, JS_VAR_DEF_LET) < 0) goto fail; } /* XXX: could avoid allocating an argument if rest is true */ idx = add_arg(ctx, fd, name); if (idx < 0) goto fail; if (next_token(s)) goto fail; if (rest) { emit_op(s, OP_rest); emit_u16(s, idx); if (fd->has_parameter_expressions) { emit_op(s, OP_dup); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } emit_op(s, OP_put_arg); emit_u16(s, idx); fd->has_simple_parameter_list = FALSE; has_opt_arg = TRUE; } else if (s->token.val == '=') { int label; fd->has_simple_parameter_list = FALSE; has_opt_arg = TRUE; if (next_token(s)) goto fail; label = new_label(s); emit_op(s, OP_get_arg); emit_u16(s, idx); emit_op(s, OP_dup); emit_op(s, OP_null); emit_op(s, OP_strict_eq); emit_goto(s, OP_if_false, label); emit_op(s, OP_drop); if (js_parse_assign_expr(s)) goto fail; set_object_name(s, name); emit_op(s, OP_dup); emit_op(s, OP_put_arg); emit_u16(s, idx); emit_label(s, label); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } else { if (!has_opt_arg) { fd->defined_arg_count++; } if (fd->has_parameter_expressions) { /* copy the argument to the argument scope */ emit_op(s, OP_get_arg); emit_u16(s, idx); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } } } else { js_parse_error(s, "missing formal parameter"); goto fail; } if (rest && s->token.val != ')') { js_parse_expect(s, ')'); goto fail; } if (s->token.val == ')') break; if (js_parse_expect(s, ',')) goto fail; } if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) || (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) { fail_accessor: js_parse_error(s, "invalid number of arguments for getter or setter"); goto fail; } } if (fd->has_parameter_expressions) { int idx; /* Copy the variables in the argument scope to the variable scope (see FunctionDeclarationInstantiation() in spec). The normal arguments are already present, so no need to copy them. */ idx = fd->scopes[fd->scope_level].first; while (idx >= 0) { JSVarDef *vd = &fd->vars[idx]; if (vd->scope_level != fd->scope_level) break; if (find_var(ctx, fd, vd->var_name) < 0) { if (add_var(ctx, fd, vd->var_name) < 0) goto fail; vd = &fd->vars[idx]; /* fd->vars may have been reallocated */ emit_op(s, OP_scope_get_var); emit_atom(s, vd->var_name); emit_u16(s, fd->scope_level); emit_op(s, OP_scope_put_var); emit_atom(s, vd->var_name); emit_u16(s, 0); } idx = vd->scope_next; } /* the argument scope has no parent, hence we don't use pop_scope(s) */ emit_op(s, OP_leave_scope); emit_u16(s, fd->scope_level); /* set the variable scope as the current scope */ fd->scope_level = 0; fd->scope_first = fd->scopes[fd->scope_level].first; } if (next_token(s)) goto fail; /* in generators, yield expression is forbidden during the parsing of the arguments */ fd->in_function_body = TRUE; push_scope(s); /* enter body scope */ fd->body_scope = fd->scope_level; if (s->token.val == TOK_ARROW && func_type == JS_PARSE_FUNC_ARROW) { if (next_token(s)) goto fail; if (s->token.val != '{') { if (js_parse_function_check_names(s, fd, func_name)) goto fail; if (js_parse_assign_expr(s)) goto fail; emit_op(s, OP_return); if (!fd->strip_source) { /* save the function source code */ /* the end of the function source code is after the last token of the function source stored into s->last_ptr */ fd->source_len = s->last_ptr - ptr; fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); if (!fd->source) goto fail; } goto done; } } if (js_parse_expect(s, '{')) goto fail; if (js_parse_directives(s)) goto fail; /* in strict_mode, check function and argument names */ if (js_parse_function_check_names(s, fd, func_name)) goto fail; while (s->token.val != '}') { if (js_parse_source_element(s)) goto fail; } if (!fd->strip_source) { /* save the function source code */ fd->source_len = s->buf_ptr - ptr; fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); if (!fd->source) goto fail; } if (next_token(s)) { /* consume the '}' */ goto fail; } /* in case there is no return, add one */ if (js_is_live_code(s)) { emit_return(s, FALSE); } done: s->cur_func = fd->parent; /* Reparse identifiers after the function is terminated so that the token is parsed in the englobing function. It could be done by just using next_token() here for normal functions, but it is necessary for arrow functions with an expression body. */ reparse_ident_token(s); /* create the function object */ { int idx; JSAtom func_name = fd->func_name; /* the real object will be set at the end of the compilation */ idx = cpool_add(s, JS_NULL); fd->parent_cpool_idx = idx; if (is_expr) { /* OP_fclosure creates the function object from the bytecode and adds the scope information */ emit_op(s, OP_fclosure); emit_u32(s, idx); if (func_name == JS_ATOM_NULL) { emit_op(s, OP_set_name); emit_u32(s, JS_ATOM_NULL); } } else if (func_type == JS_PARSE_FUNC_VAR) { emit_op(s, OP_fclosure); emit_u32(s, idx); if (create_func_var) { if (s->cur_func->is_global_var) { JSGlobalVar *hf; /* the global variable must be defined at the start of the function */ hf = add_global_var(ctx, s->cur_func, func_name); if (!hf) goto fail; /* it is considered as defined at the top level (needed for annex B.3.3.4 and B.3.3.5 checks) */ hf->scope_level = 0; hf->force_init = 1; /* store directly into global var, bypass lexical scope */ emit_op(s, OP_dup); emit_op(s, OP_scope_put_var); emit_atom(s, func_name); emit_u16(s, 0); } else { /* do not call define_var to bypass lexical scope check */ func_idx = find_var(ctx, s->cur_func, func_name); if (func_idx < 0) { func_idx = add_var(ctx, s->cur_func, func_name); if (func_idx < 0) goto fail; } /* store directly into local var, bypass lexical catch scope */ emit_op(s, OP_dup); emit_op(s, OP_scope_put_var); emit_atom(s, func_name); emit_u16(s, 0); } } if (lexical_func_idx >= 0) { /* lexical variable will be initialized upon entering scope */ s->cur_func->vars[lexical_func_idx].func_pool_idx = idx; emit_op(s, OP_drop); } else { /* store function object into its lexical name */ /* XXX: could use OP_put_loc directly */ emit_op(s, OP_scope_put_var_init); emit_atom(s, func_name); emit_u16(s, s->cur_func->scope_level); } } else { if (!s->cur_func->is_global_var) { int var_idx = define_var(s, s->cur_func, func_name, JS_VAR_DEF_VAR); if (var_idx < 0) goto fail; /* the variable will be assigned at the top of the function */ if (var_idx & ARGUMENT_VAR_OFFSET) { s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx; } else { s->cur_func->vars[var_idx].func_pool_idx = idx; } } else { JSAtom func_var_name; JSGlobalVar *hf; if (func_name == JS_ATOM_NULL) func_var_name = JS_ATOM__default_; /* export default */ else func_var_name = func_name; /* the variable will be assigned at the top of the function */ hf = add_global_var(ctx, s->cur_func, func_var_name); if (!hf) goto fail; hf->cpool_idx = idx; } } } return 0; fail: s->cur_func = fd->parent; js_free_function_def(ctx, fd); if (pfd) *pfd = NULL; return -1; } static __exception int js_parse_function_decl(JSParseState *s, JSParseFunctionEnum func_type, JSAtom func_name, const uint8_t *ptr) { return js_parse_function_decl2(s, func_type, func_name, ptr, NULL); } static __exception int js_parse_program(JSParseState *s) { JSFunctionDef *fd = s->cur_func; int idx; if (next_token(s)) return -1; if (js_parse_directives(s)) return -1; fd->is_global_var = fd->eval_type == JS_EVAL_TYPE_GLOBAL; /* hidden variable for the return value */ fd->eval_ret_idx = idx = add_var(s->ctx, fd, JS_ATOM__ret_); if (idx < 0) return -1; while (s->token.val != TOK_EOF) { if (js_parse_source_element(s)) return -1; } /* return the value of the hidden variable eval_ret_idx */ emit_op(s, OP_get_loc); emit_u16(s, fd->eval_ret_idx); emit_return(s, TRUE); return 0; } static void js_parse_init(JSContext *ctx, JSParseState *s, const char *input, size_t input_len, const char *filename) { memset(s, 0, sizeof(*s)); s->ctx = ctx; s->filename = filename; s->buf_start = s->buf_ptr = (const uint8_t *)input; s->buf_end = s->buf_ptr + input_len; s->token.val = ' '; s->token.ptr = s->buf_ptr; s->get_line_col_cache.ptr = s->buf_start; s->get_line_col_cache.buf_start = s->buf_start; s->get_line_col_cache.line_num = 0; s->get_line_col_cache.col_num = 0; } static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, JSValueConst this_obj, JSVarRef **var_refs, JSStackFrame *sf) { JSValue ret_val; uint32_t tag; tag = JS_VALUE_GET_TAG(fun_obj); if (tag == JS_TAG_FUNCTION_BYTECODE) { fun_obj = js_closure(ctx, fun_obj, var_refs, sf); ret_val = JS_CallFree(ctx, fun_obj, this_obj, 0, NULL); } else { JS_FreeValue(ctx, fun_obj); ret_val = JS_ThrowTypeError(ctx, "bytecode function expected"); } return ret_val; } JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj) { return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL); } /* '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; js_parse_init(ctx, s, input, input_len, filename); eval_type = flags & JS_EVAL_TYPE_MASK; 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; } fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, s->buf_start, &s->get_line_col_cache); if (!fd) goto fail1; s->cur_func = fd; fd->eval_type = eval_type; fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT); if (eval_type == JS_EVAL_TYPE_DIRECT) { fd->new_target_allowed = b->new_target_allowed; } else { fd->new_target_allowed = FALSE; } 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; } 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 (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: return JS_EXCEPTION; } /* 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) { BOOL backtrace_barrier = ((flags & JS_EVAL_FLAG_BACKTRACE_BARRIER) != 0); int saved_js_mode = 0; JSValue ret; if (unlikely(!ctx->eval_internal)) { return JS_ThrowTypeError(ctx, "eval is not supported"); } if (backtrace_barrier && ctx->rt->current_stack_frame) { saved_js_mode = ctx->rt->current_stack_frame->js_mode; ctx->rt->current_stack_frame->js_mode |= JS_MODE_BACKTRACE_BARRIER; } ret = ctx->eval_internal(ctx, this_obj, input, input_len, filename, flags, scope_idx); if (backtrace_barrier && ctx->rt->current_stack_frame) ctx->rt->current_stack_frame->js_mode = saved_js_mode; return ret; } static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, JSValueConst val, int flags, int scope_idx) { JSValue ret; const char *str; size_t len; if (!JS_IsString(val)) return JS_DupValue(ctx, val); str = JS_ToCStringLen(ctx, &len, val); if (!str) return JS_EXCEPTION; ret = JS_EvalInternal(ctx, this_obj, str, len, "", flags, scope_idx); JS_FreeCString(ctx, str); return ret; } 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); ret = JS_EvalInternal(ctx, this_obj, input, input_len, filename, eval_flags, -1); return ret; } 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); } /*******************************************************************/ /* object list */ typedef struct { JSObject *obj; uint32_t hash_next; /* -1 if no next entry */ } JSObjectListEntry; /* XXX: reuse it to optimize weak references */ typedef struct { JSObjectListEntry *object_tab; int object_count; int object_size; uint32_t *hash_table; uint32_t hash_size; } JSObjectList; static void js_object_list_init(JSObjectList *s) { memset(s, 0, sizeof(*s)); } static uint32_t js_object_list_get_hash(JSObject *p, uint32_t hash_size) { return ((uintptr_t)p * 3163) & (hash_size - 1); } static int js_object_list_resize_hash(JSContext *ctx, JSObjectList *s, uint32_t new_hash_size) { JSObjectListEntry *e; uint32_t i, h, *new_hash_table; new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size); if (!new_hash_table) return -1; js_free(ctx, s->hash_table); s->hash_table = new_hash_table; s->hash_size = new_hash_size; for(i = 0; i < s->hash_size; i++) { s->hash_table[i] = -1; } for(i = 0; i < s->object_count; i++) { e = &s->object_tab[i]; h = js_object_list_get_hash(e->obj, s->hash_size); e->hash_next = s->hash_table[h]; s->hash_table[h] = i; } return 0; } /* the reference count of 'obj' is not modified. Return 0 if OK, -1 if memory error */ static int js_object_list_add(JSContext *ctx, JSObjectList *s, JSObject *obj) { JSObjectListEntry *e; uint32_t h, new_hash_size; if (js_resize_array(ctx, (void *)&s->object_tab, sizeof(s->object_tab[0]), &s->object_size, s->object_count + 1)) return -1; if (unlikely((s->object_count + 1) >= s->hash_size)) { new_hash_size = max_uint32(s->hash_size, 4); while (new_hash_size <= s->object_count) new_hash_size *= 2; if (js_object_list_resize_hash(ctx, s, new_hash_size)) return -1; } e = &s->object_tab[s->object_count++]; h = js_object_list_get_hash(obj, s->hash_size); e->obj = obj; e->hash_next = s->hash_table[h]; s->hash_table[h] = s->object_count - 1; return 0; } /* return -1 if not present or the object index */ static int js_object_list_find(JSContext *ctx, JSObjectList *s, JSObject *obj) { JSObjectListEntry *e; uint32_t h, p; /* must test empty size because there is no hash table */ if (s->object_count == 0) return -1; h = js_object_list_get_hash(obj, s->hash_size); p = s->hash_table[h]; while (p != -1) { e = &s->object_tab[p]; if (e->obj == obj) return p; p = e->hash_next; } return -1; } static void js_object_list_end(JSContext *ctx, JSObjectList *s) { js_free(ctx, s->object_tab); js_free(ctx, s->hash_table); } /*******************************************************************/ /* binary object writer & reader */ typedef enum BCTagEnum { BC_TAG_NULL = 1, BC_TAG_BOOL_FALSE, BC_TAG_BOOL_TRUE, BC_TAG_INT32, BC_TAG_FLOAT64, BC_TAG_STRING, BC_TAG_OBJECT, BC_TAG_ARRAY, BC_TAG_TEMPLATE_OBJECT, BC_TAG_FUNCTION_BYTECODE, BC_TAG_MODULE, BC_TAG_OBJECT_VALUE, BC_TAG_OBJECT_REFERENCE, } BCTagEnum; #define BC_VERSION 5 typedef struct BCWriterState { JSContext *ctx; DynBuf dbuf; BOOL allow_bytecode : 8; BOOL allow_sab : 8; BOOL allow_reference : 8; uint32_t first_atom; uint32_t *atom_to_idx; int atom_to_idx_size; JSAtom *idx_to_atom; int idx_to_atom_count; int idx_to_atom_size; uint8_t **sab_tab; int sab_tab_len; int sab_tab_size; /* list of referenced objects (used if allow_reference = TRUE) */ JSObjectList object_list; } BCWriterState; #ifdef DUMP_READ_OBJECT static const char * const bc_tag_str[] = { "invalid", "null", "false", "true", "int32", "float64", "string", "object", "array", "template", "function", "module", "ObjectValue", "ObjectReference", }; #endif static inline BOOL is_be(void) { union { uint16_t a; uint8_t b; } u = {0x100}; return u.b; } static void bc_put_u8(BCWriterState *s, uint8_t v) { dbuf_putc(&s->dbuf, v); } static void bc_put_u16(BCWriterState *s, uint16_t v) { if (is_be()) v = bswap16(v); dbuf_put_u16(&s->dbuf, v); } static __maybe_unused void bc_put_u32(BCWriterState *s, uint32_t v) { if (is_be()) v = bswap32(v); dbuf_put_u32(&s->dbuf, v); } static void bc_put_u64(BCWriterState *s, uint64_t v) { if (is_be()) v = bswap64(v); dbuf_put(&s->dbuf, (uint8_t *)&v, sizeof(v)); } static void bc_put_leb128(BCWriterState *s, uint32_t v) { dbuf_put_leb128(&s->dbuf, v); } static void bc_put_sleb128(BCWriterState *s, int32_t v) { dbuf_put_sleb128(&s->dbuf, v); } static void bc_set_flags(uint32_t *pflags, int *pidx, uint32_t val, int n) { *pflags = *pflags | (val << *pidx); *pidx += n; } static int bc_atom_to_idx(BCWriterState *s, uint32_t *pres, JSAtom atom) { uint32_t v; if (atom < s->first_atom || __JS_AtomIsTaggedInt(atom)) { *pres = atom; return 0; } atom -= s->first_atom; if (atom < s->atom_to_idx_size && s->atom_to_idx[atom] != 0) { *pres = s->atom_to_idx[atom]; return 0; } if (atom >= s->atom_to_idx_size) { int old_size, i; old_size = s->atom_to_idx_size; if (js_resize_array(s->ctx, (void **)&s->atom_to_idx, sizeof(s->atom_to_idx[0]), &s->atom_to_idx_size, atom + 1)) return -1; /* XXX: could add a specific js_resize_array() function to do it */ for(i = old_size; i < s->atom_to_idx_size; i++) s->atom_to_idx[i] = 0; } if (js_resize_array(s->ctx, (void **)&s->idx_to_atom, sizeof(s->idx_to_atom[0]), &s->idx_to_atom_size, s->idx_to_atom_count + 1)) goto fail; v = s->idx_to_atom_count++; s->idx_to_atom[v] = atom + s->first_atom; v += s->first_atom; s->atom_to_idx[atom] = v; *pres = v; return 0; fail: *pres = 0; return -1; } static int bc_put_atom(BCWriterState *s, JSAtom atom) { uint32_t v; if (__JS_AtomIsTaggedInt(atom)) { v = (__JS_AtomToUInt32(atom) << 1) | 1; } else { if (bc_atom_to_idx(s, &v, atom)) return -1; v <<= 1; } bc_put_leb128(s, v); return 0; } static void bc_byte_swap(uint8_t *bc_buf, int bc_len) { int pos, len, op, fmt; pos = 0; while (pos < bc_len) { op = bc_buf[pos]; len = short_opcode_info(op).size; fmt = short_opcode_info(op).fmt; switch(fmt) { case OP_FMT_u16: case OP_FMT_i16: case OP_FMT_label16: case OP_FMT_npop: case OP_FMT_loc: case OP_FMT_arg: case OP_FMT_var_ref: put_u16(bc_buf + pos + 1, bswap16(get_u16(bc_buf + pos + 1))); break; case OP_FMT_i32: case OP_FMT_u32: case OP_FMT_const: case OP_FMT_label: case OP_FMT_atom: case OP_FMT_atom_u8: put_u32(bc_buf + pos + 1, bswap32(get_u32(bc_buf + pos + 1))); break; case OP_FMT_atom_u16: case OP_FMT_label_u16: put_u32(bc_buf + pos + 1, bswap32(get_u32(bc_buf + pos + 1))); put_u16(bc_buf + pos + 1 + 4, bswap16(get_u16(bc_buf + pos + 1 + 4))); break; case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: put_u32(bc_buf + pos + 1, bswap32(get_u32(bc_buf + pos + 1))); put_u32(bc_buf + pos + 1 + 4, bswap32(get_u32(bc_buf + pos + 1 + 4))); if (fmt == OP_FMT_atom_label_u16) { put_u16(bc_buf + pos + 1 + 4 + 4, bswap16(get_u16(bc_buf + pos + 1 + 4 + 4))); } break; case OP_FMT_npop_u16: put_u16(bc_buf + pos + 1, bswap16(get_u16(bc_buf + pos + 1))); put_u16(bc_buf + pos + 1 + 2, bswap16(get_u16(bc_buf + pos + 1 + 2))); break; default: break; } pos += len; } } static int JS_WriteFunctionBytecode(BCWriterState *s, const uint8_t *bc_buf1, int bc_len) { int pos, len, op; JSAtom atom; uint8_t *bc_buf; uint32_t val; bc_buf = js_malloc(s->ctx, bc_len); if (!bc_buf) return -1; memcpy(bc_buf, bc_buf1, bc_len); pos = 0; while (pos < bc_len) { op = bc_buf[pos]; len = short_opcode_info(op).size; switch(short_opcode_info(op).fmt) { case OP_FMT_atom: case OP_FMT_atom_u8: case OP_FMT_atom_u16: case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: atom = get_u32(bc_buf + pos + 1); if (bc_atom_to_idx(s, &val, atom)) goto fail; put_u32(bc_buf + pos + 1, val); break; default: break; } pos += len; } if (is_be()) bc_byte_swap(bc_buf, bc_len); dbuf_put(&s->dbuf, bc_buf, bc_len); js_free(s->ctx, bc_buf); return 0; fail: js_free(s->ctx, bc_buf); return -1; } static void JS_WriteString(BCWriterState *s, JSString *p) { int i; bc_put_leb128(s, ((uint32_t)p->len << 1) | p->is_wide_char); if (p->is_wide_char) { for(i = 0; i < p->len; i++) bc_put_u16(s, p->u.str16[i]); } else { dbuf_put(&s->dbuf, p->u.str8, p->len); } } static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj); static int JS_WriteFunctionTag(BCWriterState *s, JSValueConst obj) { JSFunctionBytecode *b = JS_VALUE_GET_PTR(obj); uint32_t flags; int idx, i; bc_put_u8(s, BC_TAG_FUNCTION_BYTECODE); flags = idx = 0; bc_set_flags(&flags, &idx, b->has_prototype, 1); bc_set_flags(&flags, &idx, b->has_simple_parameter_list, 1); bc_set_flags(&flags, &idx, b->is_derived_class_constructor, 1); bc_set_flags(&flags, &idx, b->func_kind, 2); bc_set_flags(&flags, &idx, b->new_target_allowed, 1); bc_set_flags(&flags, &idx, b->has_debug, 1); bc_set_flags(&flags, &idx, b->is_direct_or_indirect_eval, 1); assert(idx <= 16); bc_put_u16(s, flags); bc_put_u8(s, b->js_mode); bc_put_atom(s, b->func_name); bc_put_leb128(s, b->arg_count); bc_put_leb128(s, b->var_count); bc_put_leb128(s, b->defined_arg_count); bc_put_leb128(s, b->stack_size); bc_put_leb128(s, b->closure_var_count); bc_put_leb128(s, b->cpool_count); bc_put_leb128(s, b->byte_code_len); if (b->vardefs) { /* XXX: this field is redundant */ bc_put_leb128(s, b->arg_count + b->var_count); for(i = 0; i < b->arg_count + b->var_count; i++) { JSVarDef *vd = &b->vardefs[i]; bc_put_atom(s, vd->var_name); bc_put_leb128(s, vd->scope_level); bc_put_leb128(s, vd->scope_next + 1); flags = idx = 0; bc_set_flags(&flags, &idx, vd->var_kind, 4); bc_set_flags(&flags, &idx, vd->is_const, 1); bc_set_flags(&flags, &idx, vd->is_lexical, 1); bc_set_flags(&flags, &idx, vd->is_captured, 1); assert(idx <= 8); bc_put_u8(s, flags); } } else { bc_put_leb128(s, 0); } for(i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; bc_put_atom(s, cv->var_name); bc_put_leb128(s, cv->var_idx); flags = idx = 0; bc_set_flags(&flags, &idx, cv->is_local, 1); bc_set_flags(&flags, &idx, cv->is_arg, 1); bc_set_flags(&flags, &idx, cv->is_const, 1); bc_set_flags(&flags, &idx, cv->is_lexical, 1); bc_set_flags(&flags, &idx, cv->var_kind, 4); assert(idx <= 8); bc_put_u8(s, flags); } if (JS_WriteFunctionBytecode(s, b->byte_code_buf, b->byte_code_len)) goto fail; if (b->has_debug) { bc_put_atom(s, b->debug.filename); bc_put_leb128(s, b->debug.pc2line_len); dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len); if (b->debug.source) { bc_put_leb128(s, b->debug.source_len); dbuf_put(&s->dbuf, (uint8_t *)b->debug.source, b->debug.source_len); } else { bc_put_leb128(s, 0); } } for(i = 0; i < b->cpool_count; i++) { if (JS_WriteObjectRec(s, b->cpool[i])) goto fail; } return 0; fail: return -1; } static int JS_WriteArray(BCWriterState *s, JSValueConst obj) { JSObject *p = JS_VALUE_GET_OBJ(obj); uint32_t i, len; JSValue val; int ret; BOOL is_template; if (s->allow_bytecode && !p->extensible) { /* not extensible array: we consider it is a template when we are saving bytecode */ bc_put_u8(s, BC_TAG_TEMPLATE_OBJECT); is_template = TRUE; } else { bc_put_u8(s, BC_TAG_ARRAY); is_template = FALSE; } if (js_get_length32(s->ctx, &len, obj)) goto fail1; bc_put_leb128(s, len); for(i = 0; i < len; i++) { val = JS_GetPropertyUint32(s->ctx, obj, i); if (JS_IsException(val)) goto fail1; ret = JS_WriteObjectRec(s, val); JS_FreeValue(s->ctx, val); if (ret) goto fail1; } if (is_template) { val = JS_GetProperty(s->ctx, obj, JS_ATOM_raw); if (JS_IsException(val)) goto fail1; ret = JS_WriteObjectRec(s, val); JS_FreeValue(s->ctx, val); if (ret) goto fail1; } return 0; fail1: return -1; } static int JS_WriteObjectTag(BCWriterState *s, JSValueConst obj) { JSObject *p = JS_VALUE_GET_OBJ(obj); uint32_t i, prop_count; JSShape *sh; JSShapeProperty *pr; int pass; JSAtom atom; bc_put_u8(s, BC_TAG_OBJECT); prop_count = 0; sh = p->shape; for(pass = 0; pass < 2; pass++) { if (pass == 1) bc_put_leb128(s, prop_count); for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { atom = pr->atom; if (atom != JS_ATOM_NULL && JS_AtomIsString(s->ctx, atom) && (pr->flags & JS_PROP_ENUMERABLE)) { if (pr->flags & JS_PROP_TMASK) { JS_ThrowTypeError(s->ctx, "only value properties are supported"); goto fail; } if (pass == 0) { prop_count++; } else { bc_put_atom(s, atom); if (JS_WriteObjectRec(s, p->prop[i].u.value)) goto fail; } } } } return 0; fail: return -1; } static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj) { uint32_t tag; if (js_check_stack_overflow(s->ctx->rt, 0)) { JS_ThrowStackOverflow(s->ctx); return -1; } tag = JS_VALUE_GET_NORM_TAG(obj); switch(tag) { case JS_TAG_NULL: bc_put_u8(s, BC_TAG_NULL); break; case JS_TAG_BOOL: bc_put_u8(s, BC_TAG_BOOL_FALSE + JS_VALUE_GET_INT(obj)); break; case JS_TAG_INT: bc_put_u8(s, BC_TAG_INT32); bc_put_sleb128(s, JS_VALUE_GET_INT(obj)); break; case JS_TAG_FLOAT64: { JSFloat64Union u; bc_put_u8(s, BC_TAG_FLOAT64); u.d = JS_VALUE_GET_FLOAT64(obj); bc_put_u64(s, u.u64); } break; case JS_TAG_STRING: { JSString *p = JS_VALUE_GET_STRING(obj); bc_put_u8(s, BC_TAG_STRING); JS_WriteString(s, p); } break; case JS_TAG_STRING_ROPE: { JSValue str; str = JS_ToString(s->ctx, obj); if (JS_IsException(str)) goto fail; JS_WriteObjectRec(s, str); JS_FreeValue(s->ctx, str); } break; case JS_TAG_FUNCTION_BYTECODE: if (!s->allow_bytecode) goto invalid_tag; if (JS_WriteFunctionTag(s, obj)) goto fail; break; case JS_TAG_OBJECT: { JSObject *p = JS_VALUE_GET_OBJ(obj); int ret, idx; if (s->allow_reference) { idx = js_object_list_find(s->ctx, &s->object_list, p); if (idx >= 0) { bc_put_u8(s, BC_TAG_OBJECT_REFERENCE); bc_put_leb128(s, idx); break; } else { if (js_object_list_add(s->ctx, &s->object_list, p)) goto fail; } } else { if (p->tmp_mark) { JS_ThrowTypeError(s->ctx, "circular reference"); goto fail; } p->tmp_mark = 1; } switch(p->class_id) { case JS_CLASS_ARRAY: ret = JS_WriteArray(s, obj); break; case JS_CLASS_OBJECT: ret = JS_WriteObjectTag(s, obj); break; case JS_CLASS_NUMBER: case JS_CLASS_STRING: case JS_CLASS_BOOLEAN: bc_put_u8(s, BC_TAG_OBJECT_VALUE); ret = JS_WriteObjectRec(s, p->u.object_data); break; default: JS_ThrowTypeError(s->ctx, "unsupported object class"); ret = -1; break; } p->tmp_mark = 0; if (ret) goto fail; } break; default: invalid_tag: JS_ThrowInternalError(s->ctx, "unsupported tag (%d)", tag); goto fail; } return 0; fail: return -1; } /* create the atom table */ static int JS_WriteObjectAtoms(BCWriterState *s) { JSRuntime *rt = s->ctx->rt; DynBuf dbuf1; int i, atoms_size; dbuf1 = s->dbuf; js_dbuf_init(s->ctx, &s->dbuf); bc_put_u8(s, BC_VERSION); bc_put_leb128(s, s->idx_to_atom_count); for(i = 0; i < s->idx_to_atom_count; i++) { JSAtomStruct *p = rt->atom_array[s->idx_to_atom[i]]; JS_WriteString(s, p); } /* XXX: should check for OOM in above phase */ /* move the atoms at the start */ /* XXX: could just append dbuf1 data, but it uses more memory if dbuf1 is larger than dbuf */ atoms_size = s->dbuf.size; if (dbuf_realloc(&dbuf1, dbuf1.size + atoms_size)) goto fail; memmove(dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size); memcpy(dbuf1.buf, s->dbuf.buf, atoms_size); dbuf1.size += atoms_size; dbuf_free(&s->dbuf); s->dbuf = dbuf1; return 0; fail: dbuf_free(&dbuf1); return -1; } uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValueConst obj, int flags, uint8_t ***psab_tab, size_t *psab_tab_len) { BCWriterState ss, *s = &ss; memset(s, 0, sizeof(*s)); s->ctx = ctx; s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0); s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0); s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0); /* XXX: could use a different version when bytecode is included */ if (s->allow_bytecode) s->first_atom = JS_ATOM_END; else s->first_atom = 1; js_dbuf_init(ctx, &s->dbuf); js_object_list_init(&s->object_list); if (JS_WriteObjectRec(s, obj)) goto fail; if (JS_WriteObjectAtoms(s)) goto fail; js_object_list_end(ctx, &s->object_list); js_free(ctx, s->atom_to_idx); js_free(ctx, s->idx_to_atom); *psize = s->dbuf.size; if (psab_tab) *psab_tab = s->sab_tab; if (psab_tab_len) *psab_tab_len = s->sab_tab_len; return s->dbuf.buf; fail: js_object_list_end(ctx, &s->object_list); js_free(ctx, s->atom_to_idx); js_free(ctx, s->idx_to_atom); dbuf_free(&s->dbuf); *psize = 0; if (psab_tab) *psab_tab = NULL; if (psab_tab_len) *psab_tab_len = 0; return NULL; } uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj, int flags) { return JS_WriteObject2(ctx, psize, obj, flags, NULL, NULL); } typedef struct BCReaderState { JSContext *ctx; const uint8_t *buf_start, *ptr, *buf_end; uint32_t first_atom; uint32_t idx_to_atom_count; JSAtom *idx_to_atom; int error_state; BOOL allow_sab : 8; BOOL allow_bytecode : 8; BOOL is_rom_data : 8; BOOL allow_reference : 8; /* object references */ JSObject **objects; int objects_count; int objects_size; #ifdef DUMP_READ_OBJECT const uint8_t *ptr_last; int level; #endif } BCReaderState; #ifdef DUMP_READ_OBJECT static void __attribute__((format(printf, 2, 3))) bc_read_trace(BCReaderState *s, const char *fmt, ...) { va_list ap; int i, n, n0; if (!s->ptr_last) s->ptr_last = s->buf_start; n = n0 = 0; if (s->ptr > s->ptr_last || s->ptr == s->buf_start) { n0 = printf("%04x: ", (int)(s->ptr_last - s->buf_start)); n += n0; } for (i = 0; s->ptr_last < s->ptr; i++) { if ((i & 7) == 0 && i > 0) { printf("\n%*s", n0, ""); n = n0; } n += printf(" %02x", *s->ptr_last++); } if (*fmt == '}') s->level--; if (n < 32 + s->level * 2) { printf("%*s", 32 + s->level * 2 - n, ""); } va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); if (strchr(fmt, '{')) s->level++; } #else #define bc_read_trace(...) #endif static int bc_read_error_end(BCReaderState *s) { if (!s->error_state) { JS_ThrowSyntaxError(s->ctx, "read after the end of the buffer"); } return s->error_state = -1; } static int bc_get_u8(BCReaderState *s, uint8_t *pval) { if (unlikely(s->buf_end - s->ptr < 1)) { *pval = 0; /* avoid warning */ return bc_read_error_end(s); } *pval = *s->ptr++; return 0; } static int bc_get_u16(BCReaderState *s, uint16_t *pval) { uint16_t v; if (unlikely(s->buf_end - s->ptr < 2)) { *pval = 0; /* avoid warning */ return bc_read_error_end(s); } v = get_u16(s->ptr); if (is_be()) v = bswap16(v); *pval = v; s->ptr += 2; return 0; } static __maybe_unused int bc_get_u32(BCReaderState *s, uint32_t *pval) { uint32_t v; if (unlikely(s->buf_end - s->ptr < 4)) { *pval = 0; /* avoid warning */ return bc_read_error_end(s); } v = get_u32(s->ptr); if (is_be()) v = bswap32(v); *pval = v; s->ptr += 4; return 0; } static int bc_get_u64(BCReaderState *s, uint64_t *pval) { uint64_t v; if (unlikely(s->buf_end - s->ptr < 8)) { *pval = 0; /* avoid warning */ return bc_read_error_end(s); } v = get_u64(s->ptr); if (is_be()) v = bswap64(v); *pval = v; s->ptr += 8; return 0; } static int bc_get_leb128(BCReaderState *s, uint32_t *pval) { int ret; ret = get_leb128(pval, s->ptr, s->buf_end); if (unlikely(ret < 0)) return bc_read_error_end(s); s->ptr += ret; return 0; } static int bc_get_sleb128(BCReaderState *s, int32_t *pval) { int ret; ret = get_sleb128(pval, s->ptr, s->buf_end); if (unlikely(ret < 0)) return bc_read_error_end(s); s->ptr += ret; return 0; } /* XXX: used to read an `int` with a positive value */ static int bc_get_leb128_int(BCReaderState *s, int *pval) { return bc_get_leb128(s, (uint32_t *)pval); } static int bc_get_leb128_u16(BCReaderState *s, uint16_t *pval) { uint32_t val; if (bc_get_leb128(s, &val)) { *pval = 0; return -1; } *pval = val; return 0; } static int bc_get_buf(BCReaderState *s, uint8_t *buf, uint32_t buf_len) { if (buf_len != 0) { if (unlikely(!buf || s->buf_end - s->ptr < buf_len)) return bc_read_error_end(s); memcpy(buf, s->ptr, buf_len); s->ptr += buf_len; } return 0; } static int bc_idx_to_atom(BCReaderState *s, JSAtom *patom, uint32_t idx) { JSAtom atom; if (__JS_AtomIsTaggedInt(idx)) { atom = idx; } else if (idx < s->first_atom) { atom = JS_DupAtom(s->ctx, idx); } else { idx -= s->first_atom; if (idx >= s->idx_to_atom_count) { JS_ThrowSyntaxError(s->ctx, "invalid atom index (pos=%u)", (unsigned int)(s->ptr - s->buf_start)); *patom = JS_ATOM_NULL; return s->error_state = -1; } atom = JS_DupAtom(s->ctx, s->idx_to_atom[idx]); } *patom = atom; return 0; } static int bc_get_atom(BCReaderState *s, JSAtom *patom) { uint32_t v; if (bc_get_leb128(s, &v)) return -1; if (v & 1) { *patom = __JS_AtomFromUInt32(v >> 1); return 0; } else { return bc_idx_to_atom(s, patom, v >> 1); } } static JSString *JS_ReadString(BCReaderState *s) { uint32_t len; size_t size; BOOL is_wide_char; JSString *p; if (bc_get_leb128(s, &len)) return NULL; is_wide_char = len & 1; len >>= 1; if (len > JS_STRING_LEN_MAX) { JS_ThrowInternalError(s->ctx, "string too long"); return NULL; } p = js_alloc_string(s->ctx, len, is_wide_char); if (!p) { s->error_state = -1; return NULL; } size = (size_t)len << is_wide_char; if ((s->buf_end - s->ptr) < size) { bc_read_error_end(s); js_free_string(s->ctx->rt, p); return NULL; } memcpy(p->u.str8, s->ptr, size); s->ptr += size; if (is_wide_char) { if (is_be()) { uint32_t i; for (i = 0; i < len; i++) p->u.str16[i] = bswap16(p->u.str16[i]); } } else { p->u.str8[size] = '\0'; /* add the trailing zero for 8 bit strings */ } #ifdef DUMP_READ_OBJECT JS_DumpString(s->ctx->rt, p); printf("\n"); #endif return p; } static uint32_t bc_get_flags(uint32_t flags, int *pidx, int n) { uint32_t val; /* XXX: this does not work for n == 32 */ val = (flags >> *pidx) & ((1U << n) - 1); *pidx += n; return val; } static int JS_ReadFunctionBytecode(BCReaderState *s, JSFunctionBytecode *b, int byte_code_offset, uint32_t bc_len) { uint8_t *bc_buf; int pos, len, op; JSAtom atom; uint32_t idx; if (s->is_rom_data) { /* directly use the input buffer */ if (unlikely(s->buf_end - s->ptr < bc_len)) return bc_read_error_end(s); bc_buf = (uint8_t *)s->ptr; s->ptr += bc_len; } else { bc_buf = (void *)((uint8_t*)b + byte_code_offset); if (bc_get_buf(s, bc_buf, bc_len)) return -1; } b->byte_code_buf = bc_buf; if (is_be()) bc_byte_swap(bc_buf, bc_len); pos = 0; while (pos < bc_len) { op = bc_buf[pos]; len = short_opcode_info(op).size; switch(short_opcode_info(op).fmt) { case OP_FMT_atom: case OP_FMT_atom_u8: case OP_FMT_atom_u16: case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: idx = get_u32(bc_buf + pos + 1); if (s->is_rom_data) { /* just increment the reference count of the atom */ JS_DupAtom(s->ctx, (JSAtom)idx); } else { if (bc_idx_to_atom(s, &atom, idx)) { /* Note: the atoms will be freed up to this position */ b->byte_code_len = pos; return -1; } put_u32(bc_buf + pos + 1, atom); #ifdef DUMP_READ_OBJECT bc_read_trace(s, "at %d, fixup atom: ", pos + 1); print_atom(s->ctx, atom); printf("\n"); #endif } break; default: break; } pos += len; } return 0; } static JSValue JS_ReadObjectRec(BCReaderState *s); static int BC_add_object_ref1(BCReaderState *s, JSObject *p) { if (s->allow_reference) { if (js_resize_array(s->ctx, (void *)&s->objects, sizeof(s->objects[0]), &s->objects_size, s->objects_count + 1)) return -1; s->objects[s->objects_count++] = p; } return 0; } static int BC_add_object_ref(BCReaderState *s, JSValueConst obj) { return BC_add_object_ref1(s, JS_VALUE_GET_OBJ(obj)); } static JSValue JS_ReadFunctionTag(BCReaderState *s) { JSContext *ctx = s->ctx; JSFunctionBytecode bc, *b; JSValue obj = JS_NULL; uint16_t v16; uint8_t v8; int idx, i, local_count; int function_size, cpool_offset, byte_code_offset; int closure_var_offset, vardefs_offset; memset(&bc, 0, sizeof(bc)); bc.header.ref_count = 1; //bc.gc_header.mark = 0; if (bc_get_u16(s, &v16)) goto fail; idx = 0; bc.has_prototype = bc_get_flags(v16, &idx, 1); bc.has_simple_parameter_list = bc_get_flags(v16, &idx, 1); bc.is_derived_class_constructor = bc_get_flags(v16, &idx, 1); bc.func_kind = bc_get_flags(v16, &idx, 2); bc.new_target_allowed = bc_get_flags(v16, &idx, 1); bc.has_debug = bc_get_flags(v16, &idx, 1); bc.is_direct_or_indirect_eval = bc_get_flags(v16, &idx, 1); bc.read_only_bytecode = s->is_rom_data; if (bc_get_u8(s, &v8)) goto fail; bc.js_mode = v8; if (bc_get_atom(s, &bc.func_name)) //@ atom leak if failure goto fail; if (bc_get_leb128_u16(s, &bc.arg_count)) goto fail; if (bc_get_leb128_u16(s, &bc.var_count)) goto fail; if (bc_get_leb128_u16(s, &bc.defined_arg_count)) goto fail; if (bc_get_leb128_u16(s, &bc.stack_size)) goto fail; if (bc_get_leb128_int(s, &bc.closure_var_count)) goto fail; if (bc_get_leb128_int(s, &bc.cpool_count)) goto fail; if (bc_get_leb128_int(s, &bc.byte_code_len)) goto fail; if (bc_get_leb128_int(s, &local_count)) goto fail; if (bc.has_debug) { function_size = sizeof(*b); } else { function_size = offsetof(JSFunctionBytecode, debug); } cpool_offset = function_size; function_size += bc.cpool_count * sizeof(*bc.cpool); vardefs_offset = function_size; function_size += local_count * sizeof(*bc.vardefs); closure_var_offset = function_size; function_size += bc.closure_var_count * sizeof(*bc.closure_var); byte_code_offset = function_size; if (!bc.read_only_bytecode) { function_size += bc.byte_code_len; } b = js_mallocz(ctx, function_size); if (!b) return JS_EXCEPTION; memcpy(b, &bc, offsetof(JSFunctionBytecode, debug)); b->header.ref_count = 1; if (local_count != 0) { b->vardefs = (void *)((uint8_t*)b + vardefs_offset); } if (b->closure_var_count != 0) { b->closure_var = (void *)((uint8_t*)b + closure_var_offset); } if (b->cpool_count != 0) { b->cpool = (void *)((uint8_t*)b + cpool_offset); } add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); #ifdef DUMP_READ_OBJECT bc_read_trace(s, "name: "); print_atom(s->ctx, b->func_name); printf("\n"); #endif bc_read_trace(s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", b->arg_count, b->var_count, b->defined_arg_count, b->closure_var_count, b->cpool_count); bc_read_trace(s, "stack=%d bclen=%d locals=%d\n", b->stack_size, b->byte_code_len, local_count); if (local_count != 0) { bc_read_trace(s, "vars {\n"); for(i = 0; i < local_count; i++) { JSVarDef *vd = &b->vardefs[i]; if (bc_get_atom(s, &vd->var_name)) goto fail; if (bc_get_leb128_int(s, &vd->scope_level)) goto fail; if (bc_get_leb128_int(s, &vd->scope_next)) goto fail; vd->scope_next--; if (bc_get_u8(s, &v8)) goto fail; idx = 0; vd->var_kind = bc_get_flags(v8, &idx, 4); vd->is_const = bc_get_flags(v8, &idx, 1); vd->is_lexical = bc_get_flags(v8, &idx, 1); vd->is_captured = bc_get_flags(v8, &idx, 1); #ifdef DUMP_READ_OBJECT bc_read_trace(s, "name: "); print_atom(s->ctx, vd->var_name); printf("\n"); #endif } bc_read_trace(s, "}\n"); } if (b->closure_var_count != 0) { bc_read_trace(s, "closure vars {\n"); for(i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; int var_idx; if (bc_get_atom(s, &cv->var_name)) goto fail; if (bc_get_leb128_int(s, &var_idx)) goto fail; cv->var_idx = var_idx; if (bc_get_u8(s, &v8)) goto fail; idx = 0; cv->is_local = bc_get_flags(v8, &idx, 1); cv->is_arg = bc_get_flags(v8, &idx, 1); cv->is_const = bc_get_flags(v8, &idx, 1); cv->is_lexical = bc_get_flags(v8, &idx, 1); cv->var_kind = bc_get_flags(v8, &idx, 4); #ifdef DUMP_READ_OBJECT bc_read_trace(s, "name: "); print_atom(s->ctx, cv->var_name); printf("\n"); #endif } bc_read_trace(s, "}\n"); } { bc_read_trace(s, "bytecode {\n"); if (JS_ReadFunctionBytecode(s, b, byte_code_offset, b->byte_code_len)) goto fail; bc_read_trace(s, "}\n"); } if (b->has_debug) { /* read optional debug information */ bc_read_trace(s, "debug {\n"); if (bc_get_atom(s, &b->debug.filename)) goto fail; #ifdef DUMP_READ_OBJECT bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); #endif if (bc_get_leb128_int(s, &b->debug.pc2line_len)) goto fail; if (b->debug.pc2line_len) { b->debug.pc2line_buf = js_mallocz(ctx, b->debug.pc2line_len); if (!b->debug.pc2line_buf) goto fail; if (bc_get_buf(s, b->debug.pc2line_buf, b->debug.pc2line_len)) goto fail; } if (bc_get_leb128_int(s, &b->debug.source_len)) goto fail; if (b->debug.source_len) { bc_read_trace(s, "source: %d bytes\n", b->source_len); b->debug.source = js_mallocz(ctx, b->debug.source_len); if (!b->debug.source) goto fail; if (bc_get_buf(s, (uint8_t *)b->debug.source, b->debug.source_len)) goto fail; } bc_read_trace(s, "}\n"); } if (b->cpool_count != 0) { bc_read_trace(s, "cpool {\n"); for(i = 0; i < b->cpool_count; i++) { JSValue val; val = JS_ReadObjectRec(s); if (JS_IsException(val)) goto fail; b->cpool[i] = val; } bc_read_trace(s, "}\n"); } b->realm = JS_DupContext(ctx); return obj; fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue JS_ReadObjectTag(BCReaderState *s) { JSContext *ctx = s->ctx; JSValue obj; uint32_t prop_count, i; JSAtom atom; JSValue val; int ret; obj = JS_NewObject(ctx); if (BC_add_object_ref(s, obj)) goto fail; if (bc_get_leb128(s, &prop_count)) goto fail; for(i = 0; i < prop_count; i++) { if (bc_get_atom(s, &atom)) goto fail; #ifdef DUMP_READ_OBJECT bc_read_trace(s, "propname: "); print_atom(s->ctx, atom); printf("\n"); #endif val = JS_ReadObjectRec(s); if (JS_IsException(val)) { JS_FreeAtom(ctx, atom); goto fail; } ret = JS_DefinePropertyValue(ctx, obj, atom, val, JS_PROP_C_W_E); JS_FreeAtom(ctx, atom); if (ret < 0) goto fail; } return obj; fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue JS_ReadArray(BCReaderState *s, int tag) { JSContext *ctx = s->ctx; JSValue obj; uint32_t len, i; JSValue val; int ret, prop_flags; BOOL is_template; obj = JS_NewArray(ctx); if (BC_add_object_ref(s, obj)) goto fail; is_template = (tag == BC_TAG_TEMPLATE_OBJECT); if (bc_get_leb128(s, &len)) goto fail; for(i = 0; i < len; i++) { val = JS_ReadObjectRec(s); if (JS_IsException(val)) goto fail; if (is_template) prop_flags = JS_PROP_ENUMERABLE; else prop_flags = JS_PROP_C_W_E; ret = JS_DefinePropertyValueUint32(ctx, obj, i, val, prop_flags); if (ret < 0) goto fail; } if (is_template) { val = JS_ReadObjectRec(s); if (JS_IsException(val)) goto fail; if (!JS_IsNull(val)) { ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_raw, val, 0); if (ret < 0) goto fail; } JS_PreventExtensions(ctx, obj); } return obj; fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue JS_ReadObjectValue(BCReaderState *s) { JSContext *ctx = s->ctx; JSValue val, obj = JS_NULL; val = JS_ReadObjectRec(s); if (JS_IsException(val)) goto fail; obj = JS_ToObject(ctx, val); if (JS_IsException(obj)) goto fail; if (BC_add_object_ref(s, obj)) goto fail; JS_FreeValue(ctx, val); return obj; fail: JS_FreeValue(ctx, val); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue JS_ReadObjectRec(BCReaderState *s) { JSContext *ctx = s->ctx; uint8_t tag; JSValue obj = JS_NULL; if (js_check_stack_overflow(ctx->rt, 0)) return JS_ThrowStackOverflow(ctx); if (bc_get_u8(s, &tag)) return JS_EXCEPTION; bc_read_trace(s, "%s {\n", bc_tag_str[tag]); switch(tag) { case BC_TAG_NULL: obj = JS_NULL; break; case BC_TAG_BOOL_FALSE: case BC_TAG_BOOL_TRUE: obj = JS_NewBool(ctx, tag - BC_TAG_BOOL_FALSE); break; case BC_TAG_INT32: { int32_t val; if (bc_get_sleb128(s, &val)) return JS_EXCEPTION; bc_read_trace(s, "%d\n", val); obj = JS_NewInt32(ctx, val); } break; case BC_TAG_FLOAT64: { JSFloat64Union u; if (bc_get_u64(s, &u.u64)) return JS_EXCEPTION; bc_read_trace(s, "%g\n", u.d); obj = __JS_NewFloat64(ctx, u.d); } break; case BC_TAG_STRING: { JSString *p; p = JS_ReadString(s); if (!p) return JS_EXCEPTION; obj = JS_MKPTR(JS_TAG_STRING, p); } break; case BC_TAG_FUNCTION_BYTECODE: if (!s->allow_bytecode) goto invalid_tag; obj = JS_ReadFunctionTag(s); break; case BC_TAG_OBJECT: obj = JS_ReadObjectTag(s); break; case BC_TAG_ARRAY: case BC_TAG_TEMPLATE_OBJECT: obj = JS_ReadArray(s, tag); break; case BC_TAG_OBJECT_VALUE: obj = JS_ReadObjectValue(s); break; case BC_TAG_OBJECT_REFERENCE: { uint32_t val; if (!s->allow_reference) return JS_ThrowSyntaxError(ctx, "object references are not allowed"); if (bc_get_leb128(s, &val)) return JS_EXCEPTION; bc_read_trace(s, "%u\n", val); if (val >= s->objects_count) { return JS_ThrowSyntaxError(ctx, "invalid object reference (%u >= %u)", val, s->objects_count); } obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, s->objects[val])); } break; default: invalid_tag: return JS_ThrowSyntaxError(ctx, "invalid tag (tag=%d pos=%u)", tag, (unsigned int)(s->ptr - s->buf_start)); } bc_read_trace(s, "}\n"); return obj; } static int JS_ReadObjectAtoms(BCReaderState *s) { uint8_t v8; JSString *p; int i; JSAtom atom; if (bc_get_u8(s, &v8)) return -1; if (v8 != BC_VERSION) { JS_ThrowSyntaxError(s->ctx, "invalid version (%d expected=%d)", v8, BC_VERSION); return -1; } if (bc_get_leb128(s, &s->idx_to_atom_count)) return -1; bc_read_trace(s, "%d atom indexes {\n", s->idx_to_atom_count); if (s->idx_to_atom_count != 0) { s->idx_to_atom = js_mallocz(s->ctx, s->idx_to_atom_count * sizeof(s->idx_to_atom[0])); if (!s->idx_to_atom) return s->error_state = -1; } for(i = 0; i < s->idx_to_atom_count; i++) { p = JS_ReadString(s); if (!p) return -1; atom = JS_NewAtomStr(s->ctx, p); if (atom == JS_ATOM_NULL) return s->error_state = -1; s->idx_to_atom[i] = atom; if (s->is_rom_data && (atom != (i + s->first_atom))) s->is_rom_data = FALSE; /* atoms must be relocated */ } bc_read_trace(s, "}\n"); return 0; } static void bc_reader_free(BCReaderState *s) { int i; if (s->idx_to_atom) { for(i = 0; i < s->idx_to_atom_count; i++) { JS_FreeAtom(s->ctx, s->idx_to_atom[i]); } js_free(s->ctx, s->idx_to_atom); } js_free(s->ctx, s->objects); } JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags) { BCReaderState ss, *s = &ss; JSValue obj; ctx->binary_object_count += 1; ctx->binary_object_size += buf_len; memset(s, 0, sizeof(*s)); s->ctx = ctx; s->buf_start = buf; s->buf_end = buf + buf_len; s->ptr = buf; s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0); s->is_rom_data = ((flags & JS_READ_OBJ_ROM_DATA) != 0); s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0); s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0); if (s->allow_bytecode) s->first_atom = JS_ATOM_END; else s->first_atom = 1; if (JS_ReadObjectAtoms(s)) { obj = JS_EXCEPTION; } else { obj = JS_ReadObjectRec(s); } bc_reader_free(s); return obj; } /*******************************************************************/ /* runtime functions & objects */ static JSValue js_string_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static int check_function(JSContext *ctx, JSValueConst obj) { if (likely(JS_IsFunction(ctx, obj))) return 0; JS_ThrowTypeError(ctx, "not a function"); return -1; } static int check_exception_free(JSContext *ctx, JSValue obj) { JS_FreeValue(ctx, obj); return JS_IsException(obj); } static JSAtom find_atom(JSContext *ctx, const char *name) { JSAtom atom; int len; if (*name == '[') { name++; len = strlen(name) - 1; /* We assume 8 bit non null strings, which is the case for these symbols */ for(atom = JS_ATOM_Symbol_toPrimitive; atom < JS_ATOM_END; atom++) { JSAtomStruct *p = ctx->rt->atom_array[atom]; JSString *str = p; if (str->len == len && !memcmp(str->u.str8, name, len)) return JS_DupAtom(ctx, atom); } abort(); } else { atom = JS_NewAtom(ctx, name); } return atom; } static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) { const JSCFunctionListEntry *e = opaque; JSValue val; switch(e->def_type) { case JS_DEF_CFUNC: val = JS_NewCFunction2(ctx, e->u.func.cfunc.generic, e->name, e->u.func.length, e->u.func.cproto, e->magic); break; case JS_DEF_PROP_STRING: val = JS_NewAtomString(ctx, e->u.str); break; case JS_DEF_OBJECT: val = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len); break; default: abort(); } return val; } static int JS_InstantiateFunctionListItem(JSContext *ctx, JSValueConst obj, JSAtom atom, const JSCFunctionListEntry *e) { JSValue val; int prop_flags = e->prop_flags; switch(e->def_type) { case JS_DEF_ALIAS: /* using autoinit for aliases is not safe */ { JSAtom atom1 = find_atom(ctx, e->u.alias.name); switch (e->u.alias.base) { case -1: val = JS_GetProperty(ctx, obj, atom1); break; case 0: val = JS_GetProperty(ctx, ctx->global_obj, atom1); break; case 1: val = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], atom1); break; default: abort(); } JS_FreeAtom(ctx, atom1); if (atom == JS_ATOM_Symbol_toPrimitive) { /* Symbol.toPrimitive functions are not writable */ prop_flags = JS_PROP_CONFIGURABLE; } } break; case JS_DEF_CFUNC: if (atom == JS_ATOM_Symbol_toPrimitive) { /* Symbol.toPrimitive functions are not writable */ prop_flags = JS_PROP_CONFIGURABLE; } JS_DefineAutoInitProperty(ctx, obj, atom, JS_AUTOINIT_ID_PROP, (void *)e, prop_flags); return 0; case JS_DEF_CGETSET: /* XXX: use autoinit again ? */ case JS_DEF_CGETSET_MAGIC: { JSValue getter, setter; char buf[64]; getter = JS_NULL; if (e->u.getset.get.generic) { snprintf(buf, sizeof(buf), "get %s", e->name); getter = JS_NewCFunction2(ctx, e->u.getset.get.generic, buf, 0, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_getter_magic : JS_CFUNC_getter, e->magic); } setter = JS_NULL; if (e->u.getset.set.generic) { snprintf(buf, sizeof(buf), "set %s", e->name); setter = JS_NewCFunction2(ctx, e->u.getset.set.generic, buf, 1, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_setter_magic : JS_CFUNC_setter, e->magic); } JS_DefinePropertyGetSet(ctx, obj, atom, getter, setter, prop_flags); return 0; } break; case JS_DEF_PROP_INT32: val = JS_NewInt32(ctx, e->u.i32); break; case JS_DEF_PROP_INT64: val = JS_NewInt64(ctx, e->u.i64); break; case JS_DEF_PROP_DOUBLE: val = __JS_NewFloat64(ctx, e->u.f64); break; case JS_DEF_PROP_UNDEFINED: val = JS_NULL; break; case JS_DEF_PROP_STRING: case JS_DEF_OBJECT: JS_DefineAutoInitProperty(ctx, obj, atom, JS_AUTOINIT_ID_PROP, (void *)e, prop_flags); return 0; default: abort(); } JS_DefinePropertyValue(ctx, obj, atom, val, prop_flags); return 0; } int JS_SetPropertyFunctionList(JSContext *ctx, JSValueConst obj, const JSCFunctionListEntry *tab, int len) { int i, ret; for (i = 0; i < len; i++) { const JSCFunctionListEntry *e = &tab[i]; JSAtom atom = find_atom(ctx, e->name); if (atom == JS_ATOM_NULL) return -1; ret = JS_InstantiateFunctionListItem(ctx, obj, atom, e); JS_FreeAtom(ctx, atom); if (ret) return -1; } return 0; } /* Note: 'func_obj' is not necessarily a constructor */ static void JS_SetConstructor2(JSContext *ctx, JSValueConst func_obj, JSValueConst proto, int proto_flags, int ctor_flags) { JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, JS_DupValue(ctx, proto), proto_flags); JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, JS_DupValue(ctx, func_obj), ctor_flags); set_cycle_flag(ctx, func_obj); set_cycle_flag(ctx, proto); } void JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, JSValueConst proto) { JS_SetConstructor2(ctx, func_obj, proto, 0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } static void JS_NewGlobalCConstructor2(JSContext *ctx, JSValue func_obj, const char *name, JSValueConst proto) { JS_DefinePropertyValueStr(ctx, ctx->global_obj, name, JS_DupValue(ctx, func_obj), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_SetConstructor(ctx, func_obj, proto); JS_FreeValue(ctx, func_obj); } static JSValueConst JS_NewGlobalCConstructor(JSContext *ctx, const char *name, JSCFunction *func, int length, JSValueConst proto) { JSValue func_obj; func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor_or_func, 0); JS_NewGlobalCConstructor2(ctx, func_obj, name, proto); return func_obj; } static JSValue js_global_eval(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_EvalObject(ctx, ctx->global_obj, argv[0], JS_EVAL_TYPE_INDIRECT, -1); } static JSValue js_global_isNaN(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (unlikely(JS_ToFloat64(ctx, &d, argv[0]))) return JS_EXCEPTION; return JS_NewBool(ctx, isnan(d)); } static JSValue js_global_isFinite(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (unlikely(JS_ToFloat64(ctx, &d, argv[0]))) return JS_EXCEPTION; return JS_NewBool(ctx, isfinite(d)); } /* Object class */ static JSValue JS_ToObject(JSContext *ctx, JSValueConst val) { int tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_OBJECT: case JS_TAG_EXCEPTION: return JS_DupValue(ctx, val); default: /* Primitives cannot be converted to objects - no boxing */ return JS_ThrowTypeError(ctx, "cannot convert to record"); } } static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val) { JSValue obj = JS_ToObject(ctx, val); JS_FreeValue(ctx, val); return obj; } static int js_obj_to_desc(JSContext *ctx, JSPropertyDescriptor *d, JSValueConst desc) { JSValue val, getter, setter; int flags; if (!JS_IsObject(desc)) { JS_ThrowTypeErrorNotAnObject(ctx); return -1; } flags = 0; val = JS_NULL; getter = JS_NULL; setter = JS_NULL; if (JS_HasProperty(ctx, desc, JS_ATOM_enumerable)) { JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_enumerable); if (JS_IsException(prop)) goto fail; flags |= JS_PROP_HAS_ENUMERABLE; if (JS_ToBoolFree(ctx, prop)) flags |= JS_PROP_ENUMERABLE; } if (JS_HasProperty(ctx, desc, JS_ATOM_configurable)) { JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_configurable); if (JS_IsException(prop)) goto fail; flags |= JS_PROP_HAS_CONFIGURABLE; if (JS_ToBoolFree(ctx, prop)) flags |= JS_PROP_CONFIGURABLE; } if (JS_HasProperty(ctx, desc, JS_ATOM_value)) { flags |= JS_PROP_HAS_VALUE; val = JS_GetProperty(ctx, desc, JS_ATOM_value); if (JS_IsException(val)) goto fail; } if (JS_HasProperty(ctx, desc, JS_ATOM_writable)) { JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_writable); if (JS_IsException(prop)) goto fail; flags |= JS_PROP_HAS_WRITABLE; if (JS_ToBoolFree(ctx, prop)) flags |= JS_PROP_WRITABLE; } if (JS_HasProperty(ctx, desc, JS_ATOM_get)) { flags |= JS_PROP_HAS_GET; getter = JS_GetProperty(ctx, desc, JS_ATOM_get); if (JS_IsException(getter) || !(JS_IsNull(getter) || JS_IsFunction(ctx, getter))) { JS_ThrowTypeError(ctx, "invalid getter"); goto fail; } } if (JS_HasProperty(ctx, desc, JS_ATOM_set)) { flags |= JS_PROP_HAS_SET; setter = JS_GetProperty(ctx, desc, JS_ATOM_set); if (JS_IsException(setter) || !(JS_IsNull(setter) || JS_IsFunction(ctx, setter))) { JS_ThrowTypeError(ctx, "invalid setter"); goto fail; } } if ((flags & (JS_PROP_HAS_SET | JS_PROP_HAS_GET)) && (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE))) { JS_ThrowTypeError(ctx, "cannot have setter/getter and value or writable"); goto fail; } d->flags = flags; d->value = val; d->getter = getter; d->setter = setter; return 0; fail: JS_FreeValue(ctx, val); JS_FreeValue(ctx, getter); JS_FreeValue(ctx, setter); return -1; } static __exception int JS_DefinePropertyDesc(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValueConst desc, int flags) { JSPropertyDescriptor d; int ret; if (js_obj_to_desc(ctx, &d, desc) < 0) return -1; ret = JS_DefineProperty(ctx, obj, prop, d.value, d.getter, d.setter, d.flags | flags); js_free_desc(ctx, &d); return ret; } static __exception int JS_ObjectDefineProperties(JSContext *ctx, JSValueConst obj, JSValueConst properties) { JSValue props, desc; JSObject *p; JSPropertyEnum *atoms; uint32_t len, i; int ret = -1; if (!JS_IsObject(obj)) { JS_ThrowTypeErrorNotAnObject(ctx); return -1; } desc = JS_NULL; props = JS_ToObject(ctx, properties); if (JS_IsException(props)) return -1; p = JS_VALUE_GET_OBJ(props); /* XXX: not done in the same order as the spec */ if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK) < 0) goto exception; for(i = 0; i < len; i++) { JS_FreeValue(ctx, desc); desc = JS_GetProperty(ctx, props, atoms[i].atom); if (JS_IsException(desc)) goto exception; if (JS_DefinePropertyDesc(ctx, obj, atoms[i].atom, desc, JS_PROP_THROW) < 0) goto exception; } ret = 0; exception: JS_FreePropertyEnum(ctx, atoms, len); JS_FreeValue(ctx, props); JS_FreeValue(ctx, desc); return ret; } static JSValue js_object_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue ret; if (!JS_IsNull(new_target) && JS_VALUE_GET_OBJ(new_target) != JS_VALUE_GET_OBJ(JS_GetActiveFunction(ctx))) { ret = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); } else { int tag = JS_VALUE_GET_NORM_TAG(argv[0]); switch(tag) { case JS_TAG_NULL: ret = JS_NewObject(ctx); break; default: ret = JS_ToObject(ctx, argv[0]); break; } } return ret; } static JSValue js_object_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst proto, props; JSValue obj; proto = argv[0]; if (!JS_IsObject(proto) && !JS_IsNull(proto)) return JS_ThrowTypeError(ctx, "not a prototype"); obj = JS_NewObjectProto(ctx, proto); if (JS_IsException(obj)) return JS_EXCEPTION; props = argv[1]; if (!JS_IsNull(props)) { if (JS_ObjectDefineProperties(ctx, obj, props)) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } } return obj; } static JSValue js_object_getPrototypeOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValueConst val; val = argv[0]; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) { /* ES6 feature non compatible with ES5.1: primitive types are accepted */ if (magic || JS_VALUE_GET_TAG(val) == JS_TAG_NULL) return JS_ThrowTypeErrorNotAnObject(ctx); } return JS_GetPrototype(ctx, val); } static JSValue js_object_setPrototypeOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst obj; obj = argv[0]; if (JS_SetPrototypeInternal(ctx, obj, argv[1]) < 0) return JS_EXCEPTION; return JS_DupValue(ctx, obj); } /* magic = 1 if called as Reflect.defineProperty */ static JSValue js_object_defineProperty(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValueConst obj, prop, desc; int ret, flags; JSAtom atom; obj = argv[0]; prop = argv[1]; desc = argv[2]; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); atom = JS_ValueToAtom(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) return JS_EXCEPTION; flags = 0; if (!magic) flags |= JS_PROP_THROW; ret = JS_DefinePropertyDesc(ctx, obj, atom, desc, flags); JS_FreeAtom(ctx, atom); if (ret < 0) { return JS_EXCEPTION; } else if (magic) { return JS_NewBool(ctx, ret); } else { return JS_DupValue(ctx, obj); } } static JSValue js_object_defineProperties(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // defineProperties(obj, properties) JSValueConst obj = argv[0]; if (JS_ObjectDefineProperties(ctx, obj, argv[1])) return JS_EXCEPTION; else return JS_DupValue(ctx, obj); } static JSValue js_object_getOwnPropertyDescriptor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValueConst prop; JSAtom atom; JSValue ret, obj; JSPropertyDescriptor desc; int res, flags; if (magic) { /* Reflect.getOwnPropertyDescriptor case */ if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); obj = JS_DupValue(ctx, argv[0]); } else { obj = JS_ToObject(ctx, argv[0]); if (JS_IsException(obj)) return obj; } prop = argv[1]; atom = JS_ValueToAtom(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) goto exception; ret = JS_NULL; if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), atom); if (res < 0) goto exception; if (res) { ret = JS_NewObject(ctx); if (JS_IsException(ret)) goto exception1; flags = JS_PROP_C_W_E | JS_PROP_THROW; if (desc.flags & JS_PROP_GETSET) { if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, JS_DupValue(ctx, desc.getter), flags) < 0 || JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, JS_DupValue(ctx, desc.setter), flags) < 0) goto exception1; } else { if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, JS_DupValue(ctx, desc.value), flags) < 0 || JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable, JS_NewBool(ctx, desc.flags & JS_PROP_WRITABLE), flags) < 0) goto exception1; } if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable, JS_NewBool(ctx, desc.flags & JS_PROP_ENUMERABLE), flags) < 0 || JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable, JS_NewBool(ctx, desc.flags & JS_PROP_CONFIGURABLE), flags) < 0) goto exception1; js_free_desc(ctx, &desc); } } JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, obj); return ret; exception1: js_free_desc(ctx, &desc); JS_FreeValue(ctx, ret); exception: JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_object_getOwnPropertyDescriptors(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { //getOwnPropertyDescriptors(obj) JSValue obj, r; JSObject *p; JSPropertyEnum *props; uint32_t len, i; r = JS_NULL; obj = JS_ToObject(ctx, argv[0]); if (JS_IsException(obj)) return JS_EXCEPTION; p = JS_VALUE_GET_OBJ(obj); if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK)) goto exception; r = JS_NewObject(ctx); if (JS_IsException(r)) goto exception; for(i = 0; i < len; i++) { JSValue atomValue, desc; JSValueConst args[2]; atomValue = JS_AtomToValue(ctx, props[i].atom); if (JS_IsException(atomValue)) goto exception; args[0] = obj; args[1] = atomValue; desc = js_object_getOwnPropertyDescriptor(ctx, JS_NULL, 2, args, 0); JS_FreeValue(ctx, atomValue); if (JS_IsException(desc)) goto exception; if (!JS_IsNull(desc)) { if (JS_DefinePropertyValue(ctx, r, props[i].atom, desc, JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception; } } JS_FreePropertyEnum(ctx, props, len); JS_FreeValue(ctx, obj); return r; exception: JS_FreePropertyEnum(ctx, props, len); JS_FreeValue(ctx, obj); JS_FreeValue(ctx, r); return JS_EXCEPTION; } static JSValue JS_GetOwnPropertyNames2(JSContext *ctx, JSValueConst obj1, int flags, int kind) { JSValue obj, r, val, key, value; JSObject *p; JSPropertyEnum *atoms; uint32_t len, i, j; r = JS_NULL; val = JS_NULL; obj = JS_ToObject(ctx, obj1); if (JS_IsException(obj)) return JS_EXCEPTION; p = JS_VALUE_GET_OBJ(obj); if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, flags & ~JS_GPN_ENUM_ONLY)) goto exception; r = JS_NewArray(ctx); if (JS_IsException(r)) goto exception; for(j = i = 0; i < len; i++) { JSAtom atom = atoms[i].atom; if (flags & JS_GPN_ENUM_ONLY) { JSPropertyDescriptor desc; int res; /* Check if property is still enumerable */ res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); if (res < 0) goto exception; if (!res) continue; js_free_desc(ctx, &desc); if (!(desc.flags & JS_PROP_ENUMERABLE)) continue; } switch(kind) { default: case JS_ITERATOR_KIND_KEY: val = JS_AtomToValue(ctx, atom); if (JS_IsException(val)) goto exception; break; case JS_ITERATOR_KIND_VALUE: val = JS_GetProperty(ctx, obj, atom); if (JS_IsException(val)) goto exception; break; case JS_ITERATOR_KIND_KEY_AND_VALUE: val = JS_NewArray(ctx); if (JS_IsException(val)) goto exception; key = JS_AtomToValue(ctx, atom); if (JS_IsException(key)) goto exception1; if (JS_CreateDataPropertyUint32(ctx, val, 0, key, JS_PROP_THROW) < 0) goto exception1; value = JS_GetProperty(ctx, obj, atom); if (JS_IsException(value)) goto exception1; if (JS_CreateDataPropertyUint32(ctx, val, 1, value, JS_PROP_THROW) < 0) goto exception1; break; } if (JS_CreateDataPropertyUint32(ctx, r, j++, val, 0) < 0) goto exception; } goto done; exception1: JS_FreeValue(ctx, val); exception: JS_FreeValue(ctx, r); r = JS_EXCEPTION; done: JS_FreePropertyEnum(ctx, atoms, len); JS_FreeValue(ctx, obj); return r; } static JSValue js_object_getOwnPropertyNames(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_GetOwnPropertyNames2(ctx, argv[0], JS_GPN_STRING_MASK, JS_ITERATOR_KIND_KEY); } static JSValue js_object_keys(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int kind) { return JS_GetOwnPropertyNames2(ctx, argv[0], JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK, kind); } static JSValue js_object_isExtensible(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int reflect) { JSValueConst obj; int ret; obj = argv[0]; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { if (reflect) return JS_ThrowTypeErrorNotAnObject(ctx); else return JS_FALSE; } ret = JS_IsExtensible(ctx, obj); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_object_preventExtensions(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int reflect) { JSValueConst obj; int ret; obj = argv[0]; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { if (reflect) return JS_ThrowTypeErrorNotAnObject(ctx); else return JS_DupValue(ctx, obj); } ret = JS_PreventExtensions(ctx, obj); if (ret < 0) return JS_EXCEPTION; if (reflect) { return JS_NewBool(ctx, ret); } else { if (!ret) return JS_ThrowTypeError(ctx, "proxy preventExtensions handler returned false"); return JS_DupValue(ctx, obj); } } static JSValue js_object_hasOwn(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj; JSAtom atom; JSObject *p; BOOL ret; obj = JS_ToObject(ctx, argv[0]); if (JS_IsException(obj)) return obj; atom = JS_ValueToAtom(ctx, argv[1]); if (unlikely(atom == JS_ATOM_NULL)) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } p = JS_VALUE_GET_OBJ(obj); ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom); JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, obj); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_object_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, tag; int is_array; JSAtom atom; JSObject *p; if (JS_IsNull(this_val)) { tag = js_new_string8(ctx, "Null"); } else { obj = JS_ToObject(ctx, this_val); if (JS_IsException(obj)) return obj; is_array = JS_IsArray(ctx, obj); if (is_array < 0) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } if (is_array) { atom = JS_ATOM_Array; } else if (JS_IsFunction(ctx, obj)) { atom = JS_ATOM_Function; } else { p = JS_VALUE_GET_OBJ(obj); switch(p->class_id) { case JS_CLASS_STRING: case JS_CLASS_ERROR: case JS_CLASS_BOOLEAN: case JS_CLASS_NUMBER: case JS_CLASS_REGEXP: atom = ctx->rt->class_array[p->class_id].class_name; break; default: atom = JS_ATOM_Object; break; } } tag = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_toStringTag); JS_FreeValue(ctx, obj); if (JS_IsException(tag)) return JS_EXCEPTION; if (!JS_IsString(tag)) { JS_FreeValue(ctx, tag); tag = JS_AtomToString(ctx, atom); } } return JS_ConcatString3(ctx, "[object ", tag, "]"); } static JSValue js_object_assign(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // Object.assign(obj, source1) JSValue obj, s; int i; s = JS_NULL; obj = JS_ToObject(ctx, argv[0]); if (JS_IsException(obj)) goto exception; for (i = 1; i < argc; i++) { if (!JS_IsNull(argv[i]) && !JS_IsNull(argv[i])) { s = JS_ToObject(ctx, argv[i]); if (JS_IsException(s)) goto exception; if (JS_CopyDataProperties(ctx, obj, s, JS_NULL, TRUE)) goto exception; JS_FreeValue(ctx, s); } } return obj; exception: JS_FreeValue(ctx, obj); JS_FreeValue(ctx, s); return JS_EXCEPTION; } static JSValue js_object_seal(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int freeze_flag) { JSValueConst obj = argv[0]; JSObject *p; JSPropertyEnum *props; uint32_t len, i; int flags, desc_flags, res; if (!JS_IsObject(obj)) return JS_DupValue(ctx, obj); res = JS_PreventExtensions(ctx, obj); if (res < 0) return JS_EXCEPTION; if (!res) { return JS_ThrowTypeError(ctx, "proxy preventExtensions handler returned false"); } p = JS_VALUE_GET_OBJ(obj); flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK; if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags)) return JS_EXCEPTION; for(i = 0; i < len; i++) { JSPropertyDescriptor desc; JSAtom prop = props[i].atom; desc_flags = JS_PROP_THROW | JS_PROP_HAS_CONFIGURABLE; if (freeze_flag) { res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); if (res < 0) goto exception; if (res) { if (desc.flags & JS_PROP_WRITABLE) desc_flags |= JS_PROP_HAS_WRITABLE; js_free_desc(ctx, &desc); } } if (JS_DefineProperty(ctx, obj, prop, JS_NULL, JS_NULL, JS_NULL, desc_flags) < 0) goto exception; } JS_FreePropertyEnum(ctx, props, len); return JS_DupValue(ctx, obj); exception: JS_FreePropertyEnum(ctx, props, len); return JS_EXCEPTION; } /* return an empty string if not an object */ static JSValue js_object___getClass(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSAtom atom; JSObject *p; uint32_t tag; int class_id; tag = JS_VALUE_GET_NORM_TAG(argv[0]); if (tag == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ(argv[0]); class_id = p->class_id; atom = ctx->rt->class_array[class_id].class_name; } else { atom = JS_ATOM_empty_string; } return JS_AtomToString(ctx, atom); } static JSValue js_object_is(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_NewBool(ctx, js_same_value(ctx, argv[0], argv[1])); } static JSValue JS_SpeciesConstructor(JSContext *ctx, JSValueConst obj, JSValueConst defaultConstructor) { JSValue ctor, species; if (!JS_IsObject(obj)) return JS_ThrowTypeErrorNotAnObject(ctx); ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor); if (JS_IsException(ctor)) return ctor; if (JS_IsNull(ctor)) return JS_DupValue(ctx, defaultConstructor); if (!JS_IsObject(ctor)) { JS_FreeValue(ctx, ctor); return JS_ThrowTypeErrorNotAnObject(ctx); } species = JS_GetProperty(ctx, ctor, JS_ATOM_Symbol_species); JS_FreeValue(ctx, ctor); if (JS_IsException(species)) return species; if (JS_IsNull(species) || JS_IsNull(species)) return JS_DupValue(ctx, defaultConstructor); if (!JS_IsConstructor(ctx, species)) { JS_FreeValue(ctx, species); return JS_ThrowTypeError(ctx, "not a constructor"); } return species; } static const JSCFunctionListEntry js_object_funcs[] = { JS_CFUNC_DEF("create", 2, js_object_create ), JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 0 ), JS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf ), JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 0 ), JS_CFUNC_DEF("defineProperties", 2, js_object_defineProperties ), JS_CFUNC_DEF("getOwnPropertyNames", 1, js_object_getOwnPropertyNames ), JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 0 ), JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 0 ), JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 0 ), JS_CFUNC_DEF("getOwnPropertyDescriptors", 1, js_object_getOwnPropertyDescriptors ), JS_CFUNC_DEF("is", 2, js_object_is ), JS_CFUNC_DEF("assign", 2, js_object_assign ), JS_CFUNC_DEF("__getClass", 1, js_object___getClass ), JS_CFUNC_DEF("hasOwn", 2, js_object_hasOwn ), }; static const JSCFunctionListEntry js_object_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_object_toString ), }; /* Function class */ static JSValue js_function_proto(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_NULL; } /* XXX: add a specific eval mode so that Function("}), ({") is rejected */ static JSValue js_function_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int magic) { int i, n, ret; JSValue s, proto, obj = JS_NULL; StringBuffer b_s, *b = &b_s; string_buffer_init(ctx, b, 0); string_buffer_putc8(b, '('); string_buffer_puts8(b, "function"); string_buffer_puts8(b, " anonymous("); n = argc - 1; for(i = 0; i < n; i++) { if (i != 0) { string_buffer_putc8(b, ','); } if (string_buffer_concat_value(b, argv[i])) goto fail; } string_buffer_puts8(b, "\n) {\n"); if (n >= 0) { if (string_buffer_concat_value(b, argv[n])) goto fail; } string_buffer_puts8(b, "\n})"); s = string_buffer_end(b); if (JS_IsException(s)) goto fail1; obj = JS_EvalObject(ctx, ctx->global_obj, s, JS_EVAL_TYPE_INDIRECT, -1); JS_FreeValue(ctx, s); if (JS_IsException(obj)) goto fail1; if (!JS_IsNull(new_target)) { /* set the prototype */ proto = JS_GetProperty(ctx, new_target, JS_ATOM_prototype); if (JS_IsException(proto)) goto fail1; if (!JS_IsObject(proto)) { JSContext *realm; JS_FreeValue(ctx, proto); realm = JS_GetFunctionRealm(ctx, new_target); if (!realm) goto fail1; proto = JS_DupValue(ctx, realm->class_proto[JS_CLASS_BYTECODE_FUNCTION]); } ret = JS_SetPrototypeInternal(ctx, obj, proto); JS_FreeValue(ctx, proto); if (ret < 0) goto fail1; } return obj; fail: string_buffer_free(b); fail1: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, JSValueConst obj) { JSValue len_val; len_val = JS_GetProperty(ctx, obj, JS_ATOM_length); if (JS_IsException(len_val)) { *pres = 0; return -1; } return JS_ToUint32Free(ctx, pres, len_val); } static __exception int js_get_length64(JSContext *ctx, int64_t *pres, JSValueConst obj) { JSValue len_val; len_val = JS_GetProperty(ctx, obj, JS_ATOM_length); if (JS_IsException(len_val)) { *pres = 0; return -1; } return JS_ToLengthFree(ctx, pres, len_val); } static __exception int js_set_length64(JSContext *ctx, JSValueConst obj, int64_t len) { return JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, len)); } int JS_GetLength(JSContext *ctx, JSValueConst obj, int64_t *pres) { return js_get_length64(ctx, pres, obj); } int JS_SetLength(JSContext *ctx, JSValueConst obj, int64_t len) { return js_set_length64(ctx, obj, len); } static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len) { uint32_t i; for(i = 0; i < len; i++) { JS_FreeValue(ctx, tab[i]); } js_free(ctx, tab); } /* XXX: should use ValueArray */ static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, JSValueConst array_arg) { uint32_t len, i; int64_t len64; JSValue *tab, ret; JSObject *p; if (JS_VALUE_GET_TAG(array_arg) != JS_TAG_OBJECT) { JS_ThrowTypeError(ctx, "not a object"); return NULL; } if (js_get_length64(ctx, &len64, array_arg)) return NULL; if (len64 > JS_MAX_LOCAL_VARS) { // XXX: check for stack overflow? JS_ThrowRangeError(ctx, "too many arguments in function call (only %d allowed)", JS_MAX_LOCAL_VARS); return NULL; } len = len64; /* avoid allocating 0 bytes */ tab = js_mallocz(ctx, sizeof(tab[0]) * max_uint32(1, len)); if (!tab) return NULL; p = JS_VALUE_GET_OBJ(array_arg); if ((p->class_id == JS_CLASS_ARRAY) && p->fast_array && len == p->u.array.count) { for(i = 0; i < len; i++) { tab[i] = JS_DupValue(ctx, p->u.array.u.values[i]); } } else { for(i = 0; i < len; i++) { ret = JS_GetPropertyUint32(ctx, array_arg, i); if (JS_IsException(ret)) { free_arg_list(ctx, tab, i); return NULL; } tab[i] = ret; } } *plen = len; return tab; } /* magic value: 0 = normal apply, 2 = Reflect.apply */ static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValueConst this_arg, array_arg; uint32_t len; JSValue *tab, ret; if (check_function(ctx, this_val)) return JS_EXCEPTION; this_arg = argv[0]; array_arg = argv[1]; if (JS_VALUE_GET_TAG(array_arg) == JS_TAG_NULL && magic != 2) { return JS_Call(ctx, this_val, this_arg, 0, NULL); } tab = build_arg_list(ctx, &len, array_arg); if (!tab) return JS_EXCEPTION; ret = JS_Call(ctx, this_val, this_arg, len, (JSValueConst *)tab); free_arg_list(ctx, tab, len); return ret; } /* Error class */ static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int magic) { JSValue obj, msg, proto; JSValueConst message, options; int arg_index; if (JS_IsNull(new_target)) new_target = JS_GetActiveFunction(ctx); proto = JS_GetProperty(ctx, new_target, JS_ATOM_prototype); if (JS_IsException(proto)) return proto; if (!JS_IsObject(proto)) { JSContext *realm; JSValueConst proto1; JS_FreeValue(ctx, proto); realm = JS_GetFunctionRealm(ctx, new_target); if (!realm) return JS_EXCEPTION; if (magic < 0) { proto1 = realm->class_proto[JS_CLASS_ERROR]; } else { proto1 = realm->native_error_proto[magic]; } proto = JS_DupValue(ctx, proto1); } obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_ERROR); JS_FreeValue(ctx, proto); if (JS_IsException(obj)) return obj; arg_index = (magic == JS_AGGREGATE_ERROR); message = argv[arg_index++]; if (!JS_IsNull(message)) { msg = JS_ToString(ctx, message); if (unlikely(JS_IsException(msg))) goto exception; JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } if (arg_index < argc) { options = argv[arg_index]; if (JS_IsObject(options)) { int present = JS_HasProperty(ctx, options, JS_ATOM_cause); if (present < 0) goto exception; if (present) { JSValue cause = JS_GetProperty(ctx, options, JS_ATOM_cause); if (JS_IsException(cause)) goto exception; JS_DefinePropertyValue(ctx, obj, JS_ATOM_cause, cause, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } } } if (magic == JS_AGGREGATE_ERROR) { /* Require errors to be an array (no iterator support) */ JSValue error_list; if (JS_IsArray(ctx, argv[0])) { uint32_t len, i; if (js_get_length32(ctx, &len, argv[0])) goto exception; error_list = JS_NewArray(ctx); if (JS_IsException(error_list)) goto exception; for (i = 0; i < len; i++) { JSValue item = JS_GetPropertyUint32(ctx, argv[0], i); if (JS_IsException(item)) { JS_FreeValue(ctx, error_list); goto exception; } if (JS_DefinePropertyValueUint32(ctx, error_list, i, item, JS_PROP_C_W_E) < 0) { JS_FreeValue(ctx, error_list); goto exception; } } } else { error_list = JS_NewArray(ctx); if (JS_IsException(error_list)) goto exception; } JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, error_list, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } /* skip the Error() function in the backtrace */ build_backtrace(ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); return obj; exception: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue name, msg; if (!JS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx); name = JS_GetProperty(ctx, this_val, JS_ATOM_name); if (JS_IsNull(name)) name = JS_AtomToString(ctx, JS_ATOM_Error); else name = JS_ToStringFree(ctx, name); if (JS_IsException(name)) return JS_EXCEPTION; msg = JS_GetProperty(ctx, this_val, JS_ATOM_message); if (JS_IsNull(msg)) msg = JS_AtomToString(ctx, JS_ATOM_empty_string); else msg = JS_ToStringFree(ctx, msg); if (JS_IsException(msg)) { JS_FreeValue(ctx, name); return JS_EXCEPTION; } if (!JS_IsEmptyString(name) && !JS_IsEmptyString(msg)) name = JS_ConcatString3(ctx, "", name, ": "); return JS_ConcatString(ctx, name, msg); } static const JSCFunctionListEntry js_error_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_error_toString ), JS_PROP_STRING_DEF("name", "Error", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; /* Array */ static int JS_CopySubArray(JSContext *ctx, JSValueConst obj, int64_t to_pos, int64_t from_pos, int64_t count, int dir) { JSObject *p; int64_t i, from, to, len; JSValue val; int fromPresent; p = NULL; if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ(obj); if (p->class_id != JS_CLASS_ARRAY || !p->fast_array) { p = NULL; } } for (i = 0; i < count; ) { if (dir < 0) { from = from_pos + count - i - 1; to = to_pos + count - i - 1; } else { from = from_pos + i; to = to_pos + i; } if (p && p->fast_array && from >= 0 && from < (len = p->u.array.count) && to >= 0 && to < len) { int64_t l, j; /* Fast path for fast arrays. Since we don't look at the prototype chain, we can optimize only the cases where all the elements are present in the array. */ l = count - i; if (dir < 0) { l = min_int64(l, from + 1); l = min_int64(l, to + 1); for(j = 0; j < l; j++) { set_value(ctx, &p->u.array.u.values[to - j], JS_DupValue(ctx, p->u.array.u.values[from - j])); } } else { l = min_int64(l, len - from); l = min_int64(l, len - to); for(j = 0; j < l; j++) { set_value(ctx, &p->u.array.u.values[to + j], JS_DupValue(ctx, p->u.array.u.values[from + j])); } } i += l; } else { fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val); if (fromPresent < 0) goto exception; if (fromPresent) { if (JS_SetPropertyInt64(ctx, obj, to, val) < 0) goto exception; } else { if (JS_DeletePropertyInt64(ctx, obj, to, JS_PROP_THROW) < 0) goto exception; } i++; } } return 0; exception: return -1; } static JSValue js_array_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue obj; int i; obj = js_create_from_ctor(ctx, new_target, JS_CLASS_ARRAY); if (JS_IsException(obj)) return obj; if (argc == 1 && JS_IsNumber(argv[0])) { uint32_t len; if (JS_ToArrayLengthFree(ctx, &len, JS_DupValue(ctx, argv[0]), TRUE)) goto fail; if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewUint32(ctx, len)) < 0) goto fail; } else { for(i = 0; i < argc; i++) { if (JS_SetPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i])) < 0) goto fail; } } return obj; fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, JSValueConst len_val) { /* Always use the array constructor - no species/constructor lookup */ return js_array_constructor(ctx, JS_NULL, 1, &len_val); } static int JS_isConcatSpreadable(JSContext *ctx, JSValueConst obj) { JSValue val; if (!JS_IsObject(obj)) return FALSE; val = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_isConcatSpreadable); if (JS_IsException(val)) return -1; if (!JS_IsNull(val)) return JS_ToBoolFree(ctx, val); return JS_IsArray(ctx, obj); } static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, val; int64_t len, n; JSValue *arrp; uint32_t count; int res; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; res = FALSE; if (len > 0) { n = 0; if (argc > 1) { if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len)) goto exception; } if (js_get_fast_array(ctx, obj, &arrp, &count)) { for (; n < count; n++) { if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), JS_DupValue(ctx, arrp[n]), JS_EQ_SAME_VALUE_ZERO)) { res = TRUE; goto done; } } } for (; n < len; n++) { val = JS_GetPropertyInt64(ctx, obj, n); if (JS_IsException(val)) goto exception; if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_SAME_VALUE_ZERO)) { res = TRUE; break; } } } done: JS_FreeValue(ctx, obj); return JS_NewBool(ctx, res); exception: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, method, ret; obj = JS_ToObject(ctx, this_val); if (JS_IsException(obj)) return JS_EXCEPTION; method = JS_GetProperty(ctx, obj, JS_ATOM_join); if (JS_IsException(method)) { ret = JS_EXCEPTION; } else if (!JS_IsFunction(ctx, method)) { /* Use intrinsic Object.prototype.toString */ JS_FreeValue(ctx, method); ret = js_object_toString(ctx, obj, 0, NULL); } else { ret = JS_CallFree(ctx, method, obj, 0, NULL); } JS_FreeValue(ctx, obj); return ret; } static JSValue js_array_pop(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int shift) { JSValue obj, res = JS_NULL; int64_t len, newLen; JSValue *arrp; uint32_t count32; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; newLen = 0; if (len > 0) { newLen = len - 1; /* Special case fast arrays */ if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { JSObject *p = JS_VALUE_GET_OBJ(obj); if (shift) { res = arrp[0]; memmove(arrp, arrp + 1, (count32 - 1) * sizeof(*arrp)); p->u.array.count--; } else { res = arrp[count32 - 1]; p->u.array.count--; } } else { if (shift) { res = JS_GetPropertyInt64(ctx, obj, 0); if (JS_IsException(res)) goto exception; if (JS_CopySubArray(ctx, obj, 0, 1, len - 1, +1)) goto exception; } else { res = JS_GetPropertyInt64(ctx, obj, newLen); if (JS_IsException(res)) goto exception; } if (JS_DeletePropertyInt64(ctx, obj, newLen, JS_PROP_THROW) < 0) goto exception; } } if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0) goto exception; JS_FreeValue(ctx, obj); return res; exception: JS_FreeValue(ctx, res); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int unshift) { JSValue obj; int i; int64_t len, from, newLen; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; newLen = len + argc; if (newLen > MAX_SAFE_INTEGER) { JS_ThrowTypeError(ctx, "Array loo long"); goto exception; } from = len; if (unshift && argc > 0) { if (JS_CopySubArray(ctx, obj, argc, 0, len, -1)) goto exception; from = 0; } for(i = 0; i < argc; i++) { if (JS_SetPropertyInt64(ctx, obj, from + i, JS_DupValue(ctx, argv[i])) < 0) goto exception; } if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, newLen)) < 0) goto exception; JS_FreeValue(ctx, obj); return JS_NewInt64(ctx, newLen); exception: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int splice) { JSValue obj, arr, val, len_val; int64_t len, start, k, final, n, count, del_count, new_len; int kPresent; JSValue *arrp; uint32_t count32, i, item_count; arr = JS_NULL; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) goto exception; if (splice) { if (argc == 0) { item_count = 0; del_count = 0; } else if (argc == 1) { item_count = 0; del_count = len - start; } else { item_count = argc - 2; if (JS_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0)) goto exception; } if (len + item_count - del_count > MAX_SAFE_INTEGER) { JS_ThrowTypeError(ctx, "Array loo long"); goto exception; } count = del_count; } else { item_count = 0; /* avoid warning */ final = len; if (!JS_IsNull(argv[1])) { if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len)) goto exception; } count = max_int64(final - start, 0); } len_val = JS_NewInt64(ctx, count); arr = JS_ArraySpeciesCreate(ctx, obj, len_val); JS_FreeValue(ctx, len_val); if (JS_IsException(arr)) goto exception; k = start; final = start + count; n = 0; /* The fast array test on arr ensures that JS_CreateDataPropertyUint32() won't modify obj in case arr is an exotic object */ /* Special case fast arrays */ if (js_get_fast_array(ctx, obj, &arrp, &count32) && js_is_fast_array(ctx, arr)) { /* XXX: should share code with fast array constructor */ for (; k < final && k < count32; k++, n++) { if (JS_CreateDataPropertyUint32(ctx, arr, n, JS_DupValue(ctx, arrp[k]), JS_PROP_THROW) < 0) goto exception; } } /* Copy the remaining elements if any (handle case of inherited properties) */ for (; k < final; k++, n++) { kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val); if (kPresent < 0) goto exception; if (kPresent) { if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0) goto exception; } } if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0) goto exception; if (splice) { new_len = len + item_count - del_count; if (item_count != del_count) { if (JS_CopySubArray(ctx, obj, start + item_count, start + del_count, len - (start + del_count), item_count <= del_count ? +1 : -1) < 0) goto exception; for (k = len; k-- > new_len; ) { if (JS_DeletePropertyInt64(ctx, obj, k, JS_PROP_THROW) < 0) goto exception; } } for (i = 0; i < item_count; i++) { if (JS_SetPropertyInt64(ctx, obj, start + i, JS_DupValue(ctx, argv[i + 2])) < 0) goto exception; } if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, new_len)) < 0) goto exception; } JS_FreeValue(ctx, obj); return arr; exception: JS_FreeValue(ctx, obj); JS_FreeValue(ctx, arr); return JS_EXCEPTION; } static int64_t JS_FlattenIntoArray(JSContext *ctx, JSValueConst target, JSValueConst source, int64_t sourceLen, int64_t targetIndex, int depth, JSValueConst mapperFunction, JSValueConst thisArg) { JSValue element; int64_t sourceIndex, elementLen; int present, is_array; if (js_check_stack_overflow(ctx->rt, 0)) { JS_ThrowStackOverflow(ctx); return -1; } for (sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { present = JS_TryGetPropertyInt64(ctx, source, sourceIndex, &element); if (present < 0) return -1; if (!present) continue; if (!JS_IsNull(mapperFunction)) { JSValueConst args[3] = { element, JS_NewInt64(ctx, sourceIndex), source }; element = JS_Call(ctx, mapperFunction, thisArg, 3, args); JS_FreeValue(ctx, (JSValue)args[0]); JS_FreeValue(ctx, (JSValue)args[1]); if (JS_IsException(element)) return -1; } if (depth > 0) { is_array = JS_IsArray(ctx, element); if (is_array < 0) goto fail; if (is_array) { if (js_get_length64(ctx, &elementLen, element) < 0) goto fail; targetIndex = JS_FlattenIntoArray(ctx, target, element, elementLen, targetIndex, depth - 1, JS_NULL, JS_NULL); if (targetIndex < 0) goto fail; JS_FreeValue(ctx, element); continue; } } if (targetIndex >= MAX_SAFE_INTEGER) { JS_ThrowTypeError(ctx, "Array too long"); goto fail; } if (JS_DefinePropertyValueInt64(ctx, target, targetIndex, element, JS_PROP_C_W_E | JS_PROP_THROW) < 0) return -1; targetIndex++; } return targetIndex; fail: JS_FreeValue(ctx, element); return -1; } static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab) { JSValue obj; int i; obj = JS_NewArray(ctx); if (JS_IsException(obj)) return JS_EXCEPTION; for(i = 0; i < len; i++) { if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, tab[i]), 0) < 0) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } } return obj; } static const JSCFunctionListEntry js_array_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_array_toString ), JS_CFUNC_MAGIC_DEF("pop", 0, js_array_pop, 0 ), JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ), JS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1 ), JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ), JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ), }; /* String */ static int js_string_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc, JSValueConst obj, JSAtom prop) { JSObject *p; JSString *p1; uint32_t idx, ch; /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ if (__JS_AtomIsTaggedInt(prop)) { p = JS_VALUE_GET_OBJ(obj); if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { p1 = JS_VALUE_GET_STRING(p->u.object_data); idx = __JS_AtomToUInt32(prop); if (idx < p1->len) { if (desc) { ch = string_get(p1, idx); desc->flags = JS_PROP_ENUMERABLE; desc->value = js_new_string_char(ctx, ch); desc->getter = JS_NULL; desc->setter = JS_NULL; } return TRUE; } } } return FALSE; } static int js_string_define_own_property(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { uint32_t idx; JSObject *p; JSString *p1, *p2; if (__JS_AtomIsTaggedInt(prop)) { idx = __JS_AtomToUInt32(prop); p = JS_VALUE_GET_OBJ(this_obj); if (JS_VALUE_GET_TAG(p->u.object_data) != JS_TAG_STRING) goto def; p1 = JS_VALUE_GET_STRING(p->u.object_data); if (idx >= p1->len) goto def; if (!check_define_prop_flags(JS_PROP_ENUMERABLE, flags)) goto fail; /* check that the same value is configured */ if (flags & JS_PROP_HAS_VALUE) { if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) goto fail; p2 = JS_VALUE_GET_STRING(val); if (p2->len != 1) goto fail; if (string_get(p1, idx) != string_get(p2, 0)) { fail: return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); } } return TRUE; } else { def: return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, flags | JS_PROP_NO_EXOTIC); } } static int js_string_delete_property(JSContext *ctx, JSValueConst obj, JSAtom prop) { uint32_t idx; if (__JS_AtomIsTaggedInt(prop)) { idx = __JS_AtomToUInt32(prop); if (idx < js_string_obj_get_length(ctx, obj)) { return FALSE; } } return TRUE; } static const JSClassExoticMethods js_string_exotic_methods = { .get_own_property = js_string_get_own_property, .define_own_property = js_string_define_own_property, .delete_property = js_string_delete_property, }; static JSValue js_string_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue val, obj; if (argc == 0) { val = JS_AtomToString(ctx, JS_ATOM_empty_string); } else { if (JS_IsNull(new_target) && JS_IsSymbol(argv[0])) { JSAtomStruct *p = JS_VALUE_GET_PTR(argv[0]); val = JS_ConcatString3(ctx, "Symbol(", JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p)), ")"); } else { val = JS_ToString(ctx, argv[0]); } if (JS_IsException(val)) return val; } if (!JS_IsNull(new_target)) { JSString *p1 = JS_VALUE_GET_STRING(val); obj = js_create_from_ctor(ctx, new_target, JS_CLASS_STRING); if (JS_IsException(obj)) { JS_FreeValue(ctx, val); } else { JS_SetObjectData(ctx, obj, val); JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, JS_NewInt32(ctx, p1->len), 0); } return obj; } else { return val; } } static JSValue js_thisStringValue(JSContext *ctx, JSValueConst this_val) { if (JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING || JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING_ROPE) return JS_DupValue(ctx, this_val); if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(this_val); if (p->class_id == JS_CLASS_STRING) { if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) return JS_DupValue(ctx, p->u.object_data); } } return JS_ThrowTypeError(ctx, "not a string"); } static int string_cmp(JSString *p1, JSString *p2, int x1, int x2, int len) { int i, c1, c2; for (i = 0; i < len; i++) { if ((c1 = string_get(p1, x1 + i)) != (c2 = string_get(p2, x2 + i))) return c1 - c2; } return 0; } static int string_indexof_char(JSString *p, int c, int from) { /* assuming 0 <= from <= p->len */ int i, len = p->len; if (p->is_wide_char) { for (i = from; i < len; i++) { if (p->u.str16[i] == c) return i; } } else { if ((c & ~0xff) == 0) { for (i = from; i < len; i++) { if (p->u.str8[i] == (uint8_t)c) return i; } } } return -1; } static int string_indexof(JSString *p1, JSString *p2, int from) { /* assuming 0 <= from <= p1->len */ int c, i, j, len1 = p1->len, len2 = p2->len; if (len2 == 0) return from; for (i = from, c = string_get(p2, 0); i + len2 <= len1; i = j + 1) { j = string_indexof_char(p1, c, i); if (j < 0 || j + len2 > len1) break; if (!string_cmp(p1, p2, j + 1, 1, len2 - 1)) return j; } return -1; } static int64_t string_advance_index(JSString *p, int64_t index, BOOL unicode) { if (!unicode || index >= p->len || !p->is_wide_char) { index++; } else { int index32 = (int)index; string_getc(p, &index32); index = index32; } return index; } /* return the position of the first invalid character in the string or -1 if none */ static int js_string_find_invalid_codepoint(JSString *p) { int i; if (!p->is_wide_char) return -1; for(i = 0; i < p->len; i++) { uint32_t c = p->u.str16[i]; if (is_surrogate(c)) { if (is_hi_surrogate(c) && (i + 1) < p->len && is_lo_surrogate(p->u.str16[i + 1])) { i++; } else { return i; } } } return -1; } /* return < 0 if exception or TRUE/FALSE */ static int js_is_regexp(JSContext *ctx, JSValueConst obj); static JSValue js_string___GetSubstitution(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // GetSubstitution(matched, str, position, captures, namedCaptures, rep) JSValueConst matched, str, captures, namedCaptures, rep; JSValue capture, name, s; uint32_t position, len, matched_len, captures_len; int i, j, j0, k, k1; int c, c1; StringBuffer b_s, *b = &b_s; JSString *sp, *rp; matched = argv[0]; str = argv[1]; captures = argv[3]; namedCaptures = argv[4]; rep = argv[5]; if (!JS_IsString(rep) || !JS_IsString(str)) return JS_ThrowTypeError(ctx, "not a string"); sp = JS_VALUE_GET_STRING(str); rp = JS_VALUE_GET_STRING(rep); string_buffer_init(ctx, b, 0); captures_len = 0; if (!JS_IsNull(captures)) { if (js_get_length32(ctx, &captures_len, captures)) goto exception; } if (js_get_length32(ctx, &matched_len, matched)) goto exception; if (JS_ToUint32(ctx, &position, argv[2]) < 0) goto exception; len = rp->len; i = 0; for(;;) { j = string_indexof_char(rp, '$', i); if (j < 0 || j + 1 >= len) break; string_buffer_concat(b, rp, i, j); j0 = j++; c = string_get(rp, j++); if (c == '$') { string_buffer_putc8(b, '$'); } else if (c == '&') { if (string_buffer_concat_value(b, matched)) goto exception; } else if (c == '`') { string_buffer_concat(b, sp, 0, position); } else if (c == '\'') { string_buffer_concat(b, sp, position + matched_len, sp->len); } else if (c >= '0' && c <= '9') { k = c - '0'; if (j < len) { c1 = string_get(rp, j); if (c1 >= '0' && c1 <= '9') { /* This behavior is specified in ES6 and refined in ECMA 2019 */ /* ECMA 2019 does not have the extra test, but Test262 S15.5.4.11_A3_T1..3 require this behavior */ k1 = k * 10 + c1 - '0'; if (k1 >= 1 && k1 < captures_len) { k = k1; j++; } } } if (k >= 1 && k < captures_len) { s = JS_GetPropertyInt64(ctx, captures, k); if (JS_IsException(s)) goto exception; if (!JS_IsNull(s)) { if (string_buffer_concat_value_free(b, s)) goto exception; } } else { goto norep; } } else if (c == '<' && !JS_IsNull(namedCaptures)) { k = string_indexof_char(rp, '>', j); if (k < 0) goto norep; name = js_sub_string(ctx, rp, j, k); if (JS_IsException(name)) goto exception; capture = JS_GetPropertyValue(ctx, namedCaptures, name); if (JS_IsException(capture)) goto exception; if (!JS_IsNull(capture)) { if (string_buffer_concat_value_free(b, capture)) goto exception; } j = k + 1; } else { norep: string_buffer_concat(b, rp, j0, j); } i = j; } string_buffer_concat(b, rp, i, rp->len); return string_buffer_end(b); exception: string_buffer_free(b); return JS_EXCEPTION; } /* also used for String.prototype.valueOf */ static JSValue js_string_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_thisStringValue(ctx, this_val); } static JSValue js_string_concat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue r; int i; /* XXX: Use more efficient method */ /* XXX: This method is OK if r has a single refcount */ /* XXX: should use string_buffer? */ r = JS_ToStringCheckObject(ctx, this_val); for (i = 0; i < argc; i++) { if (JS_IsException(r)) break; r = JS_ConcatString(ctx, r, JS_DupValue(ctx, argv[i])); } return r; } static const JSCFunctionListEntry js_string_proto_funcs[] = { JS_CFUNC_DEF("concat", 1, js_string_concat), JS_CFUNC_DEF("toString", 0, js_string_toString ), JS_CFUNC_DEF("valueOf", 0, js_string_toString ), }; /* RegExp */ static void js_regexp_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSRegExp *re = &p->u.regexp; JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->bytecode)); JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->pattern)); } /* create a string containing the RegExp bytecode */ static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, JSValueConst flags) { const char *str; int re_flags, mask; uint8_t *re_bytecode_buf; size_t i, len; int re_bytecode_len; JSValue ret; char error_msg[64]; re_flags = 0; if (!JS_IsNull(flags)) { str = JS_ToCStringLen(ctx, &len, flags); if (!str) return JS_EXCEPTION; /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */ for (i = 0; i < len; i++) { switch(str[i]) { case 'd': mask = LRE_FLAG_INDICES; break; case 'g': mask = LRE_FLAG_GLOBAL; break; case 'i': mask = LRE_FLAG_IGNORECASE; break; case 'm': mask = LRE_FLAG_MULTILINE; break; case 's': mask = LRE_FLAG_DOTALL; break; case 'u': mask = LRE_FLAG_UNICODE; break; case 'v': mask = LRE_FLAG_UNICODE_SETS; break; case 'y': mask = LRE_FLAG_STICKY; break; default: goto bad_flags; } if ((re_flags & mask) != 0) { bad_flags: JS_FreeCString(ctx, str); goto bad_flags1; } re_flags |= mask; } JS_FreeCString(ctx, str); } /* 'u' and 'v' cannot be both set */ if ((re_flags & LRE_FLAG_UNICODE_SETS) && (re_flags & LRE_FLAG_UNICODE)) { bad_flags1: return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); } str = JS_ToCStringLen2(ctx, &len, pattern, !(re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS))); if (!str) return JS_EXCEPTION; re_bytecode_buf = lre_compile(&re_bytecode_len, error_msg, sizeof(error_msg), str, len, re_flags, ctx); JS_FreeCString(ctx, str); if (!re_bytecode_buf) { JS_ThrowSyntaxError(ctx, "%s", error_msg); return JS_EXCEPTION; } ret = js_new_string8_len(ctx, (const char *)re_bytecode_buf, re_bytecode_len); js_free(ctx, re_bytecode_buf); return ret; } /* create a RegExp object from a string containing the RegExp bytecode and the source pattern */ static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, JSValue pattern, JSValue bc) { JSValue obj; JSObject *p; JSRegExp *re; /* sanity check */ if (JS_VALUE_GET_TAG(bc) != JS_TAG_STRING || JS_VALUE_GET_TAG(pattern) != JS_TAG_STRING) { JS_ThrowTypeError(ctx, "string expected"); fail: JS_FreeValue(ctx, bc); JS_FreeValue(ctx, pattern); return JS_EXCEPTION; } obj = js_create_from_ctor(ctx, ctor, JS_CLASS_REGEXP); if (JS_IsException(obj)) goto fail; p = JS_VALUE_GET_OBJ(obj); re = &p->u.regexp; re->pattern = JS_VALUE_GET_STRING(pattern); re->bytecode = JS_VALUE_GET_STRING(bc); JS_DefinePropertyValue(ctx, obj, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0), JS_PROP_WRITABLE); return obj; } static JSRegExp *js_get_regexp(JSContext *ctx, JSValueConst obj, BOOL throw_error) { if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(obj); if (p->class_id == JS_CLASS_REGEXP) return &p->u.regexp; } if (throw_error) { JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_REGEXP); } return NULL; } /* return < 0 if exception or TRUE/FALSE */ static int js_is_regexp(JSContext *ctx, JSValueConst obj) { JSValue m; if (!JS_IsObject(obj)) return FALSE; m = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_match); if (JS_IsException(m)) return -1; if (!JS_IsNull(m)) return JS_ToBoolFree(ctx, m); return js_get_regexp(ctx, obj, FALSE) != NULL; } static JSValue js_regexp_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue pattern, flags, bc, val; JSValueConst pat, flags1; JSRegExp *re; int pat_is_regexp; pat = argv[0]; flags1 = argv[1]; pat_is_regexp = js_is_regexp(ctx, pat); if (pat_is_regexp < 0) return JS_EXCEPTION; if (JS_IsNull(new_target)) { /* called as a function */ new_target = JS_GetActiveFunction(ctx); if (pat_is_regexp && JS_IsNull(flags1)) { JSValue ctor; BOOL res; ctor = JS_GetProperty(ctx, pat, JS_ATOM_constructor); if (JS_IsException(ctor)) return ctor; res = js_same_value(ctx, ctor, new_target); JS_FreeValue(ctx, ctor); if (res) return JS_DupValue(ctx, pat); } } re = js_get_regexp(ctx, pat, FALSE); if (re) { pattern = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern)); if (JS_IsNull(flags1)) { bc = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->bytecode)); goto no_compilation; } else { flags = JS_ToString(ctx, flags1); if (JS_IsException(flags)) goto fail; } } else { flags = JS_NULL; if (pat_is_regexp) { pattern = JS_GetProperty(ctx, pat, JS_ATOM_source); if (JS_IsException(pattern)) goto fail; if (JS_IsNull(flags1)) { flags = JS_GetProperty(ctx, pat, JS_ATOM_flags); if (JS_IsException(flags)) goto fail; } else { flags = JS_DupValue(ctx, flags1); } } else { pattern = JS_DupValue(ctx, pat); flags = JS_DupValue(ctx, flags1); } if (JS_IsNull(pattern)) { pattern = JS_AtomToString(ctx, JS_ATOM_empty_string); } else { val = pattern; pattern = JS_ToString(ctx, val); JS_FreeValue(ctx, val); if (JS_IsException(pattern)) goto fail; } } bc = js_compile_regexp(ctx, pattern, flags); if (JS_IsException(bc)) goto fail; JS_FreeValue(ctx, flags); no_compilation: return js_regexp_constructor_internal(ctx, new_target, pattern, bc); fail: JS_FreeValue(ctx, pattern); JS_FreeValue(ctx, flags); return JS_EXCEPTION; } static JSValue js_regexp_compile(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSRegExp *re1, *re; JSValueConst pattern1, flags1; JSValue bc, pattern; re = js_get_regexp(ctx, this_val, TRUE); if (!re) return JS_EXCEPTION; pattern1 = argv[0]; flags1 = argv[1]; re1 = js_get_regexp(ctx, pattern1, FALSE); if (re1) { if (!JS_IsNull(flags1)) return JS_ThrowTypeError(ctx, "flags must be undefined"); pattern = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re1->pattern)); bc = JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re1->bytecode)); } else { bc = JS_NULL; if (JS_IsNull(pattern1)) pattern = JS_AtomToString(ctx, JS_ATOM_empty_string); else pattern = JS_ToString(ctx, pattern1); if (JS_IsException(pattern)) goto fail; bc = js_compile_regexp(ctx, pattern, flags1); if (JS_IsException(bc)) goto fail; } JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern)); JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->bytecode)); re->pattern = JS_VALUE_GET_STRING(pattern); re->bytecode = JS_VALUE_GET_STRING(bc); if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) return JS_EXCEPTION; return JS_DupValue(ctx, this_val); fail: JS_FreeValue(ctx, pattern); JS_FreeValue(ctx, bc); return JS_EXCEPTION; } static JSValue js_regexp_get_source(JSContext *ctx, JSValueConst this_val) { JSRegExp *re; JSString *p; StringBuffer b_s, *b = &b_s; int i, n, c, c2, bra; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP])) goto empty_regex; re = js_get_regexp(ctx, this_val, TRUE); if (!re) return JS_EXCEPTION; p = re->pattern; if (p->len == 0) { empty_regex: return js_new_string8(ctx, "(?:)"); } string_buffer_init2(ctx, b, p->len, p->is_wide_char); /* Escape '/' and newline sequences as needed */ bra = 0; for (i = 0, n = p->len; i < n;) { c2 = -1; switch (c = string_get(p, i++)) { case '\\': if (i < n) c2 = string_get(p, i++); break; case ']': bra = 0; break; case '[': if (!bra) { if (i < n && string_get(p, i) == ']') c2 = string_get(p, i++); bra = 1; } break; case '\n': c = '\\'; c2 = 'n'; break; case '\r': c = '\\'; c2 = 'r'; break; case '/': if (!bra) { c = '\\'; c2 = '/'; } break; } string_buffer_putc16(b, c); if (c2 >= 0) string_buffer_putc16(b, c2); } return string_buffer_end(b); } static JSValue js_regexp_get_flag(JSContext *ctx, JSValueConst this_val, int mask) { JSRegExp *re; int flags; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); re = js_get_regexp(ctx, this_val, FALSE); if (!re) { if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP])) return JS_NULL; else return JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_REGEXP); } flags = lre_get_flags(re->bytecode->u.str8); return JS_NewBool(ctx, flags & mask); } #define RE_FLAG_COUNT 8 static JSValue js_regexp_get_flags(JSContext *ctx, JSValueConst this_val) { char str[RE_FLAG_COUNT], *p = str; int res, i; static const int flag_atom[RE_FLAG_COUNT] = { JS_ATOM_hasIndices, JS_ATOM_global, JS_ATOM_ignoreCase, JS_ATOM_multiline, JS_ATOM_dotAll, JS_ATOM_unicode, JS_ATOM_unicodeSets, JS_ATOM_sticky, }; static const char flag_char[RE_FLAG_COUNT] = { 'd', 'g', 'i', 'm', 's', 'u', 'v', 'y' }; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); for(i = 0; i < RE_FLAG_COUNT; i++) { res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, flag_atom[i])); if (res < 0) goto exception; if (res) *p++ = flag_char[i]; } return JS_NewStringLen(ctx, str, p - str); exception: return JS_EXCEPTION; } static JSValue js_regexp_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue pattern, flags; StringBuffer b_s, *b = &b_s; if (!JS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx); string_buffer_init(ctx, b, 0); string_buffer_putc8(b, '/'); pattern = JS_GetProperty(ctx, this_val, JS_ATOM_source); if (string_buffer_concat_value_free(b, pattern)) goto fail; string_buffer_putc8(b, '/'); flags = JS_GetProperty(ctx, this_val, JS_ATOM_flags); if (string_buffer_concat_value_free(b, flags)) goto fail; return string_buffer_end(b); fail: string_buffer_free(b); return JS_EXCEPTION; } int lre_check_stack_overflow(void *opaque, size_t alloca_size) { JSContext *ctx = opaque; return js_check_stack_overflow(ctx->rt, alloca_size); } int lre_check_timeout(void *opaque) { JSContext *ctx = opaque; JSRuntime *rt = ctx->rt; return (rt->interrupt_handler && rt->interrupt_handler(rt, rt->interrupt_opaque)); } void *lre_realloc(void *opaque, void *ptr, size_t size) { JSContext *ctx = opaque; /* No JS exception is raised here */ return js_realloc_rt(ctx->rt, ptr, size); } static JSValue js_regexp_exec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSRegExp *re = js_get_regexp(ctx, this_val, TRUE); JSString *str; JSValue t, ret, str_val, obj, val, groups; JSValue indices, indices_groups; uint8_t *re_bytecode; uint8_t **capture, *str_buf; int rc, capture_count, shift, i, re_flags; int64_t last_index; const char *group_name_ptr; if (!re) return JS_EXCEPTION; str_val = JS_ToString(ctx, argv[0]); if (JS_IsException(str_val)) return JS_EXCEPTION; ret = JS_EXCEPTION; obj = JS_NULL; groups = JS_NULL; indices = JS_NULL; indices_groups = JS_NULL; capture = NULL; val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex); if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val)) goto fail; re_bytecode = re->bytecode->u.str8; re_flags = lre_get_flags(re_bytecode); if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { last_index = 0; } str = JS_VALUE_GET_STRING(str_val); capture_count = lre_get_capture_count(re_bytecode); if (capture_count > 0) { capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2); if (!capture) goto fail; } shift = str->is_wide_char; str_buf = str->u.str8; if (last_index > str->len) { rc = 2; } else { rc = lre_exec(capture, re_bytecode, str_buf, last_index, str->len, shift, ctx); } if (rc != 1) { if (rc >= 0) { if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) goto fail; } } else { if (rc == LRE_RET_TIMEOUT) { JS_ThrowInterrupted(ctx); } else { JS_ThrowInternalError(ctx, "out of memory in regexp execution"); } goto fail; } } else { int prop_flags; if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, JS_NewInt32(ctx, (capture[1] - str_buf) >> shift)) < 0) goto fail; } obj = JS_NewArray(ctx); if (JS_IsException(obj)) goto fail; prop_flags = JS_PROP_C_W_E | JS_PROP_THROW; group_name_ptr = lre_get_groupnames(re_bytecode); if (group_name_ptr) { groups = JS_NewObjectProto(ctx, JS_NULL); if (JS_IsException(groups)) goto fail; } if (re_flags & LRE_FLAG_INDICES) { indices = JS_NewArray(ctx); if (JS_IsException(indices)) goto fail; if (group_name_ptr) { indices_groups = JS_NewObjectProto(ctx, JS_NULL); if (JS_IsException(indices_groups)) goto fail; } } for(i = 0; i < capture_count; i++) { const char *name = NULL; uint8_t **match = &capture[2 * i]; int start = -1; int end = -1; JSValue val; if (group_name_ptr && i > 0) { if (*group_name_ptr) name = group_name_ptr; group_name_ptr += strlen(group_name_ptr) + 1; } if (match[0] && match[1]) { start = (match[0] - str_buf) >> shift; end = (match[1] - str_buf) >> shift; } if (!JS_IsNull(indices)) { val = JS_NULL; if (start != -1) { val = JS_NewArray(ctx); if (JS_IsException(val)) goto fail; if (JS_DefinePropertyValueUint32(ctx, val, 0, JS_NewInt32(ctx, start), prop_flags) < 0) { JS_FreeValue(ctx, val); goto fail; } if (JS_DefinePropertyValueUint32(ctx, val, 1, JS_NewInt32(ctx, end), prop_flags) < 0) { JS_FreeValue(ctx, val); goto fail; } } if (name && !JS_IsNull(indices_groups)) { val = JS_DupValue(ctx, val); if (JS_DefinePropertyValueStr(ctx, indices_groups, name, val, prop_flags) < 0) { JS_FreeValue(ctx, val); goto fail; } } if (JS_DefinePropertyValueUint32(ctx, indices, i, val, prop_flags) < 0) { goto fail; } } val = JS_NULL; if (start != -1) { val = js_sub_string(ctx, str, start, end); if (JS_IsException(val)) goto fail; } if (name) { if (JS_DefinePropertyValueStr(ctx, groups, name, JS_DupValue(ctx, val), prop_flags) < 0) { JS_FreeValue(ctx, val); goto fail; } } if (JS_DefinePropertyValueUint32(ctx, obj, i, val, prop_flags) < 0) goto fail; } t = JS_NewInt32(ctx, (capture[0] - str_buf) >> shift); if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_index, t, prop_flags) < 0) goto fail; t = str_val, str_val = JS_NULL; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_input, t, prop_flags) < 0) goto fail; t = groups, groups = JS_NULL; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_groups, t, prop_flags) < 0) { goto fail; } if (!JS_IsNull(indices)) { t = indices_groups, indices_groups = JS_NULL; if (JS_DefinePropertyValue(ctx, indices, JS_ATOM_groups, t, prop_flags) < 0) { goto fail; } t = indices, indices = JS_NULL; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_indices, t, prop_flags) < 0) { goto fail; } } } ret = obj; obj = JS_NULL; fail: JS_FreeValue(ctx, indices_groups); JS_FreeValue(ctx, indices); JS_FreeValue(ctx, str_val); JS_FreeValue(ctx, groups); JS_FreeValue(ctx, obj); js_free(ctx, capture); return ret; } /* delete portions of a string that match a given regex */ static JSValue JS_RegExpDelete(JSContext *ctx, JSValueConst this_val, JSValueConst arg) { JSRegExp *re = js_get_regexp(ctx, this_val, TRUE); JSString *str; JSValue str_val, val; uint8_t *re_bytecode; int ret; uint8_t **capture, *str_buf; int capture_count, shift, re_flags; int next_src_pos, start, end; int64_t last_index; StringBuffer b_s, *b = &b_s; if (!re) return JS_EXCEPTION; string_buffer_init(ctx, b, 0); capture = NULL; str_val = JS_ToString(ctx, arg); if (JS_IsException(str_val)) goto fail; str = JS_VALUE_GET_STRING(str_val); re_bytecode = re->bytecode->u.str8; re_flags = lre_get_flags(re_bytecode); if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { last_index = 0; } else { val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex); if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val)) goto fail; } capture_count = lre_get_capture_count(re_bytecode); if (capture_count > 0) { capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2); if (!capture) goto fail; } shift = str->is_wide_char; str_buf = str->u.str8; next_src_pos = 0; for (;;) { if (last_index > str->len) break; ret = lre_exec(capture, re_bytecode, str_buf, last_index, str->len, shift, ctx); if (ret != 1) { if (ret >= 0) { if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) goto fail; } } else { if (ret == LRE_RET_TIMEOUT) { JS_ThrowInterrupted(ctx); } else { JS_ThrowInternalError(ctx, "out of memory in regexp execution"); } goto fail; } break; } start = (capture[0] - str_buf) >> shift; end = (capture[1] - str_buf) >> shift; last_index = end; if (next_src_pos < start) { if (string_buffer_concat(b, str, next_src_pos, start)) goto fail; } next_src_pos = end; if (!(re_flags & LRE_FLAG_GLOBAL)) { if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, JS_NewInt32(ctx, end)) < 0) goto fail; break; } if (end == start) { if (!(re_flags & LRE_FLAG_UNICODE) || (unsigned)end >= str->len || !str->is_wide_char) { end++; } else { string_getc(str, &end); } } last_index = end; } if (string_buffer_concat(b, str, next_src_pos, str->len)) goto fail; JS_FreeValue(ctx, str_val); js_free(ctx, capture); return string_buffer_end(b); fail: JS_FreeValue(ctx, str_val); js_free(ctx, capture); string_buffer_free(b); return JS_EXCEPTION; } static JSValue JS_RegExpExec(JSContext *ctx, JSValueConst r, JSValueConst s) { JSValue method, ret; method = JS_GetProperty(ctx, r, JS_ATOM_exec); if (JS_IsException(method)) return method; if (JS_IsFunction(ctx, method)) { ret = JS_CallFree(ctx, method, r, 1, &s); if (JS_IsException(ret)) return ret; if (!JS_IsObject(ret) && !JS_IsNull(ret)) { JS_FreeValue(ctx, ret); return JS_ThrowTypeError(ctx, "RegExp exec method must return an object or null"); } return ret; } JS_FreeValue(ctx, method); return js_regexp_exec(ctx, r, 1, &s); } static int js_is_standard_regexp(JSContext *ctx, JSValueConst rx) { JSValue val; int res; val = JS_GetProperty(ctx, rx, JS_ATOM_constructor); if (JS_IsException(val)) return -1; // rx.constructor === RegExp res = js_same_value(ctx, val, ctx->regexp_ctor); JS_FreeValue(ctx, val); if (res) { val = JS_GetProperty(ctx, rx, JS_ATOM_exec); if (JS_IsException(val)) return -1; // rx.exec === RE_exec res = JS_IsCFunction(ctx, val, js_regexp_exec, 0); JS_FreeValue(ctx, val); } return res; } static const JSCFunctionListEntry js_regexp_proto_funcs[] = { JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ), JS_CGETSET_DEF("source", js_regexp_get_source, NULL ), JS_CGETSET_MAGIC_DEF("global", js_regexp_get_flag, NULL, LRE_FLAG_GLOBAL ), JS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, LRE_FLAG_IGNORECASE ), JS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, LRE_FLAG_MULTILINE ), JS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, LRE_FLAG_DOTALL ), JS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE ), JS_CGETSET_MAGIC_DEF("unicodeSets", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE_SETS ), JS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, LRE_FLAG_STICKY ), JS_CGETSET_MAGIC_DEF("hasIndices", js_regexp_get_flag, NULL, LRE_FLAG_INDICES ), JS_CFUNC_DEF("exec", 1, js_regexp_exec ), JS_CFUNC_DEF("compile", 2, js_regexp_compile ), JS_CFUNC_DEF("toString", 0, js_regexp_toString ), }; void JS_AddIntrinsicRegExpCompiler(JSContext *ctx) { ctx->compile_regexp = js_compile_regexp; } void JS_AddIntrinsicRegExp(JSContext *ctx) { JSValueConst obj; JS_AddIntrinsicRegExpCompiler(ctx); ctx->class_proto[JS_CLASS_REGEXP] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP], js_regexp_proto_funcs, countof(js_regexp_proto_funcs)); obj = JS_NewGlobalCConstructor(ctx, "RegExp", js_regexp_constructor, 2, ctx->class_proto[JS_CLASS_REGEXP]); ctx->regexp_ctor = JS_DupValue(ctx, obj); } /* JSON */ static int json_parse_expect(JSParseState *s, int tok) { if (s->token.val != tok) { /* XXX: dump token correctly in all cases */ return js_parse_error(s, "expecting '%c'", tok); } return json_next_token(s); } static JSValue json_parse_value(JSParseState *s) { JSContext *ctx = s->ctx; JSValue val = JS_NULL; int ret; switch(s->token.val) { case '{': { JSValue prop_val; JSAtom prop_name; if (json_next_token(s)) goto fail; val = JS_NewObject(ctx); if (JS_IsException(val)) goto fail; if (s->token.val != '}') { for(;;) { if (s->token.val == TOK_STRING) { prop_name = JS_ValueToAtom(ctx, s->token.u.str.str); if (prop_name == JS_ATOM_NULL) goto fail; } else if (s->ext_json && s->token.val == TOK_IDENT) { prop_name = JS_DupAtom(ctx, s->token.u.ident.atom); } else { js_parse_error(s, "expecting property name"); goto fail; } if (json_next_token(s)) goto fail1; if (json_parse_expect(s, ':')) goto fail1; prop_val = json_parse_value(s); if (JS_IsException(prop_val)) { fail1: JS_FreeAtom(ctx, prop_name); goto fail; } ret = JS_DefinePropertyValue(ctx, val, prop_name, prop_val, JS_PROP_C_W_E); JS_FreeAtom(ctx, prop_name); if (ret < 0) goto fail; if (s->token.val != ',') break; if (json_next_token(s)) goto fail; if (s->ext_json && s->token.val == '}') break; } } if (json_parse_expect(s, '}')) goto fail; } break; case '[': { JSValue el; uint32_t idx; if (json_next_token(s)) goto fail; val = JS_NewArray(ctx); if (JS_IsException(val)) goto fail; if (s->token.val != ']') { idx = 0; for(;;) { el = json_parse_value(s); if (JS_IsException(el)) goto fail; ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E); if (ret < 0) goto fail; if (s->token.val != ',') break; if (json_next_token(s)) goto fail; idx++; if (s->ext_json && s->token.val == ']') break; } } if (json_parse_expect(s, ']')) goto fail; } break; case TOK_STRING: val = JS_DupValue(ctx, s->token.u.str.str); if (json_next_token(s)) goto fail; break; case TOK_NUMBER: val = s->token.u.num.val; if (json_next_token(s)) goto fail; break; case TOK_IDENT: if (s->token.u.ident.atom == JS_ATOM_false || s->token.u.ident.atom == JS_ATOM_true) { val = JS_NewBool(ctx, s->token.u.ident.atom == JS_ATOM_true); } else if (s->token.u.ident.atom == JS_ATOM_null) { val = JS_NULL; } else if (s->token.u.ident.atom == JS_ATOM_NaN && s->ext_json) { /* Note: json5 identifier handling is ambiguous e.g. is '{ NaN: 1 }' a valid JSON5 production ? */ val = JS_NewFloat64(s->ctx, NAN); } else if (s->token.u.ident.atom == JS_ATOM_Infinity && s->ext_json) { val = JS_NewFloat64(s->ctx, INFINITY); } else { goto def_token; } if (json_next_token(s)) goto fail; break; default: def_token: if (s->token.val == TOK_EOF) { js_parse_error(s, "Unexpected end of JSON input"); } else { js_parse_error(s, "unexpected token: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); } goto fail; } return val; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, const char *filename, int flags) { JSParseState s1, *s = &s1; JSValue val = JS_NULL; js_parse_init(ctx, s, buf, buf_len, filename); s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0); if (json_next_token(s)) goto fail; val = json_parse_value(s); if (JS_IsException(val)) goto fail; if (s->token.val != TOK_EOF) { if (js_parse_error(s, "unexpected data at the end")) goto fail; } return val; fail: JS_FreeValue(ctx, val); free_token(s, &s->token); return JS_EXCEPTION; } JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename) { return JS_ParseJSON2(ctx, buf, buf_len, filename, 0); } static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, JSAtom name, JSValueConst reviver) { JSValue val, new_el, name_val, res; JSValueConst args[2]; int ret, is_array; uint32_t i, len = 0; JSAtom prop; JSPropertyEnum *atoms = NULL; if (js_check_stack_overflow(ctx->rt, 0)) { return JS_ThrowStackOverflow(ctx); } val = JS_GetProperty(ctx, holder, name); if (JS_IsException(val)) return val; if (JS_IsObject(val)) { is_array = JS_IsArray(ctx, val); if (is_array < 0) goto fail; if (is_array) { if (js_get_length32(ctx, &len, val)) goto fail; } else { ret = JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, JS_VALUE_GET_OBJ(val), JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK); if (ret < 0) goto fail; } for(i = 0; i < len; i++) { if (is_array) { prop = JS_NewAtomUInt32(ctx, i); if (prop == JS_ATOM_NULL) goto fail; } else { prop = JS_DupAtom(ctx, atoms[i].atom); } new_el = internalize_json_property(ctx, val, prop, reviver); if (JS_IsException(new_el)) { JS_FreeAtom(ctx, prop); goto fail; } if (JS_IsNull(new_el)) { ret = JS_DeleteProperty(ctx, val, prop, 0); } else { ret = JS_DefinePropertyValue(ctx, val, prop, new_el, JS_PROP_C_W_E); } JS_FreeAtom(ctx, prop); if (ret < 0) goto fail; } } JS_FreePropertyEnum(ctx, atoms, len); atoms = NULL; name_val = JS_AtomToValue(ctx, name); if (JS_IsException(name_val)) goto fail; args[0] = name_val; args[1] = val; res = JS_Call(ctx, reviver, holder, 2, args); JS_FreeValue(ctx, name_val); JS_FreeValue(ctx, val); return res; fail: JS_FreePropertyEnum(ctx, atoms, len); JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, root; JSValueConst reviver; const char *str; size_t len; str = JS_ToCStringLen(ctx, &len, argv[0]); if (!str) return JS_EXCEPTION; obj = JS_ParseJSON(ctx, str, len, ""); JS_FreeCString(ctx, str); if (JS_IsException(obj)) return obj; if (argc > 1 && JS_IsFunction(ctx, argv[1])) { reviver = argv[1]; root = JS_NewObject(ctx); if (JS_IsException(root)) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj, JS_PROP_C_W_E) < 0) { JS_FreeValue(ctx, root); return JS_EXCEPTION; } obj = internalize_json_property(ctx, root, JS_ATOM_empty_string, reviver); JS_FreeValue(ctx, root); } return obj; } typedef struct JSONStringifyContext { JSValueConst replacer_func; JSValue stack; JSValue property_list; JSValue gap; JSValue empty; StringBuffer *b; } JSONStringifyContext; static JSValue JS_ToQuotedStringFree(JSContext *ctx, JSValue val) { JSValue r = JS_ToQuotedString(ctx, val); JS_FreeValue(ctx, val); return r; } static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc, JSValueConst holder, JSValue val, JSValueConst key) { JSValue v; JSValueConst args[2]; /* check for object.toJSON method */ /* ECMA specifies this is done only for Object and BigInt */ if (JS_IsObject(val)) { JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON); if (JS_IsException(f)) goto exception; if (JS_IsFunction(ctx, f)) { v = JS_CallFree(ctx, f, val, 1, &key); JS_FreeValue(ctx, val); val = v; if (JS_IsException(val)) goto exception; } else { JS_FreeValue(ctx, f); } } if (!JS_IsNull(jsc->replacer_func)) { args[0] = key; args[1] = val; v = JS_Call(ctx, jsc->replacer_func, holder, 2, args); JS_FreeValue(ctx, val); val = v; if (JS_IsException(val)) goto exception; } switch (JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_OBJECT: if (JS_IsFunction(ctx, val)) break; case JS_TAG_STRING: case JS_TAG_STRING_ROPE: case JS_TAG_INT: case JS_TAG_FLOAT64: case JS_TAG_BOOL: case JS_TAG_NULL: case JS_TAG_EXCEPTION: return val; default: break; } JS_FreeValue(ctx, val); return JS_NULL; exception: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, JSValueConst holder, JSValue val, JSValueConst indent) { JSValue indent1, sep, sep1, tab, v, prop; JSObject *p; int64_t i, len; int cl, ret; BOOL has_content; indent1 = JS_NULL; sep = JS_NULL; sep1 = JS_NULL; tab = JS_NULL; prop = JS_NULL; if (js_check_stack_overflow(ctx->rt, 0)) { JS_ThrowStackOverflow(ctx); goto exception; } if (JS_IsObject(val)) { p = JS_VALUE_GET_OBJ(val); cl = p->class_id; if (cl == JS_CLASS_STRING) { val = JS_ToStringFree(ctx, val); if (JS_IsException(val)) goto exception; goto concat_primitive; } else if (cl == JS_CLASS_NUMBER) { val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) goto exception; goto concat_primitive; } else if (cl == JS_CLASS_BOOLEAN) { /* This will thow the same error as for the primitive object */ set_value(ctx, &val, JS_DupValue(ctx, p->u.object_data)); goto concat_primitive; } v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val); if (JS_IsException(v)) goto exception; if (JS_ToBoolFree(ctx, v)) { JS_ThrowTypeError(ctx, "circular reference"); goto exception; } indent1 = JS_ConcatString(ctx, JS_DupValue(ctx, indent), JS_DupValue(ctx, jsc->gap)); if (JS_IsException(indent1)) goto exception; if (!JS_IsEmptyString(jsc->gap)) { sep = JS_ConcatString3(ctx, "\n", JS_DupValue(ctx, indent1), ""); if (JS_IsException(sep)) goto exception; sep1 = js_new_string8(ctx, " "); if (JS_IsException(sep1)) goto exception; } else { sep = JS_DupValue(ctx, jsc->empty); sep1 = JS_DupValue(ctx, jsc->empty); } v = js_array_push(ctx, jsc->stack, 1, (JSValueConst *)&val, 0); if (check_exception_free(ctx, v)) goto exception; ret = JS_IsArray(ctx, val); if (ret < 0) goto exception; if (ret) { if (js_get_length64(ctx, &len, val)) goto exception; string_buffer_putc8(jsc->b, '['); for(i = 0; i < len; i++) { if (i > 0) string_buffer_putc8(jsc->b, ','); string_buffer_concat_value(jsc->b, sep); v = JS_GetPropertyInt64(ctx, val, i); if (JS_IsException(v)) goto exception; /* XXX: could do this string conversion only when needed */ prop = JS_ToStringFree(ctx, JS_NewInt64(ctx, i)); if (JS_IsException(prop)) goto exception; v = js_json_check(ctx, jsc, val, v, prop); JS_FreeValue(ctx, prop); prop = JS_NULL; if (JS_IsException(v)) goto exception; if (JS_IsNull(v)) v = JS_NULL; if (js_json_to_str(ctx, jsc, val, v, indent1)) goto exception; } if (len > 0 && !JS_IsEmptyString(jsc->gap)) { string_buffer_putc8(jsc->b, '\n'); string_buffer_concat_value(jsc->b, indent); } string_buffer_putc8(jsc->b, ']'); } else { if (!JS_IsNull(jsc->property_list)) tab = JS_DupValue(ctx, jsc->property_list); else tab = js_object_keys(ctx, JS_NULL, 1, (JSValueConst *)&val, JS_ITERATOR_KIND_KEY); if (JS_IsException(tab)) goto exception; if (js_get_length64(ctx, &len, tab)) goto exception; string_buffer_putc8(jsc->b, '{'); has_content = FALSE; for(i = 0; i < len; i++) { JS_FreeValue(ctx, prop); prop = JS_GetPropertyInt64(ctx, tab, i); if (JS_IsException(prop)) goto exception; v = JS_GetPropertyValue(ctx, val, JS_DupValue(ctx, prop)); if (JS_IsException(v)) goto exception; v = js_json_check(ctx, jsc, val, v, prop); if (JS_IsException(v)) goto exception; if (!JS_IsNull(v)) { if (has_content) string_buffer_putc8(jsc->b, ','); prop = JS_ToQuotedStringFree(ctx, prop); if (JS_IsException(prop)) { JS_FreeValue(ctx, v); goto exception; } string_buffer_concat_value(jsc->b, sep); string_buffer_concat_value(jsc->b, prop); string_buffer_putc8(jsc->b, ':'); string_buffer_concat_value(jsc->b, sep1); if (js_json_to_str(ctx, jsc, val, v, indent1)) goto exception; has_content = TRUE; } } if (has_content && !JS_IsEmptyString(jsc->gap)) { string_buffer_putc8(jsc->b, '\n'); string_buffer_concat_value(jsc->b, indent); } string_buffer_putc8(jsc->b, '}'); } if (check_exception_free(ctx, js_array_pop(ctx, jsc->stack, 0, NULL, 0))) goto exception; JS_FreeValue(ctx, val); JS_FreeValue(ctx, tab); JS_FreeValue(ctx, sep); JS_FreeValue(ctx, sep1); JS_FreeValue(ctx, indent1); JS_FreeValue(ctx, prop); return 0; } concat_primitive: switch (JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_STRING: case JS_TAG_STRING_ROPE: val = JS_ToQuotedStringFree(ctx, val); if (JS_IsException(val)) goto exception; goto concat_value; case JS_TAG_FLOAT64: if (!isfinite(JS_VALUE_GET_FLOAT64(val))) { val = JS_NULL; } goto concat_value; case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: concat_value: return string_buffer_concat_value_free(jsc->b, val); default: JS_FreeValue(ctx, val); return 0; } exception: JS_FreeValue(ctx, val); JS_FreeValue(ctx, tab); JS_FreeValue(ctx, sep); JS_FreeValue(ctx, sep1); JS_FreeValue(ctx, indent1); JS_FreeValue(ctx, prop); return -1; } JSValue JS_JSONStringify(JSContext *ctx, JSValueConst obj, JSValueConst replacer, JSValueConst space0) { StringBuffer b_s; JSONStringifyContext jsc_s, *jsc = &jsc_s; JSValue val, v, space, ret, wrapper; int res; int64_t i, j, n; jsc->replacer_func = JS_NULL; jsc->stack = JS_NULL; jsc->property_list = JS_NULL; jsc->gap = JS_NULL; jsc->b = &b_s; jsc->empty = JS_AtomToString(ctx, JS_ATOM_empty_string); ret = JS_NULL; wrapper = JS_NULL; string_buffer_init(ctx, jsc->b, 0); jsc->stack = JS_NewArray(ctx); if (JS_IsException(jsc->stack)) goto exception; if (JS_IsFunction(ctx, replacer)) { jsc->replacer_func = replacer; } else { res = JS_IsArray(ctx, replacer); if (res < 0) goto exception; if (res) { /* XXX: enumeration is not fully correct */ jsc->property_list = JS_NewArray(ctx); if (JS_IsException(jsc->property_list)) goto exception; if (js_get_length64(ctx, &n, replacer)) goto exception; for (i = j = 0; i < n; i++) { JSValue present; v = JS_GetPropertyInt64(ctx, replacer, i); if (JS_IsException(v)) goto exception; if (JS_IsObject(v)) { JSObject *p = JS_VALUE_GET_OBJ(v); if (p->class_id == JS_CLASS_STRING || p->class_id == JS_CLASS_NUMBER) { v = JS_ToStringFree(ctx, v); if (JS_IsException(v)) goto exception; } else { JS_FreeValue(ctx, v); continue; } } else if (JS_IsNumber(v)) { v = JS_ToStringFree(ctx, v); if (JS_IsException(v)) goto exception; } else if (!JS_IsString(v)) { JS_FreeValue(ctx, v); continue; } present = js_array_includes(ctx, jsc->property_list, 1, (JSValueConst *)&v); if (JS_IsException(present)) { JS_FreeValue(ctx, v); goto exception; } if (!JS_ToBoolFree(ctx, present)) { JS_SetPropertyInt64(ctx, jsc->property_list, j++, v); } else { JS_FreeValue(ctx, v); } } } } space = JS_DupValue(ctx, space0); if (JS_IsObject(space)) { JSObject *p = JS_VALUE_GET_OBJ(space); if (p->class_id == JS_CLASS_NUMBER) { space = JS_ToNumberFree(ctx, space); } else if (p->class_id == JS_CLASS_STRING) { space = JS_ToStringFree(ctx, space); } if (JS_IsException(space)) { JS_FreeValue(ctx, space); goto exception; } } if (JS_IsNumber(space)) { int n; if (JS_ToInt32Clamp(ctx, &n, space, 0, 10, 0)) goto exception; jsc->gap = js_new_string8_len(ctx, " ", n); } else if (JS_IsString(space)) { JSString *p = JS_VALUE_GET_STRING(space); jsc->gap = js_sub_string(ctx, p, 0, min_int(p->len, 10)); } else { jsc->gap = JS_DupValue(ctx, jsc->empty); } JS_FreeValue(ctx, space); if (JS_IsException(jsc->gap)) goto exception; wrapper = JS_NewObject(ctx); if (JS_IsException(wrapper)) goto exception; if (JS_DefinePropertyValue(ctx, wrapper, JS_ATOM_empty_string, JS_DupValue(ctx, obj), JS_PROP_C_W_E) < 0) goto exception; val = JS_DupValue(ctx, obj); val = js_json_check(ctx, jsc, wrapper, val, jsc->empty); if (JS_IsException(val)) goto exception; if (JS_IsNull(val)) { ret = JS_NULL; goto done1; } if (js_json_to_str(ctx, jsc, wrapper, val, jsc->empty)) goto exception; ret = string_buffer_end(jsc->b); goto done; exception: ret = JS_EXCEPTION; done1: string_buffer_free(jsc->b); done: JS_FreeValue(ctx, wrapper); JS_FreeValue(ctx, jsc->empty); JS_FreeValue(ctx, jsc->gap); JS_FreeValue(ctx, jsc->property_list); JS_FreeValue(ctx, jsc->stack); return ret; } static JSValue js_json_stringify(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // stringify(val, replacer, space) return JS_JSONStringify(ctx, argv[0], argv[1], argv[2]); } static const JSCFunctionListEntry js_json_funcs[] = { JS_CFUNC_DEF("parse", 2, js_json_parse ), JS_CFUNC_DEF("stringify", 3, js_json_stringify ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_json_obj[] = { JS_OBJECT_DEF("JSON", js_json_funcs, countof(js_json_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; void JS_AddIntrinsicJSON(JSContext *ctx) { /* add JSON as autoinit object */ JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_json_obj, countof(js_json_obj)); } /* Symbol */ static JSValue js_symbol_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue str; JSString *p; if (!JS_IsNull(new_target)) return JS_ThrowTypeError(ctx, "not a constructor"); if (argc == 0 || JS_IsNull(argv[0])) { p = NULL; } else { str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return JS_EXCEPTION; p = JS_VALUE_GET_STRING(str); } return JS_NewSymbol(ctx, p, JS_ATOM_TYPE_SYMBOL); } static JSValue js_thisSymbolValue(JSContext *ctx, JSValueConst this_val) { if (JS_VALUE_GET_TAG(this_val) == JS_TAG_SYMBOL) return JS_DupValue(ctx, this_val); if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(this_val); if (p->class_id == JS_CLASS_SYMBOL) { if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_SYMBOL) return JS_DupValue(ctx, p->u.object_data); } } return JS_ThrowTypeError(ctx, "not a symbol"); } static JSValue js_symbol_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; val = js_thisSymbolValue(ctx, this_val); if (JS_IsException(val)) return val; /* XXX: use JS_ToStringInternal() with a flags */ ret = js_string_constructor(ctx, JS_NULL, 1, (JSValueConst *)&val); JS_FreeValue(ctx, val); return ret; } static JSValue js_symbol_valueOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_thisSymbolValue(ctx, this_val); } static JSValue js_symbol_get_description(JSContext *ctx, JSValueConst this_val) { JSValue val, ret; JSAtomStruct *p; val = js_thisSymbolValue(ctx, this_val); if (JS_IsException(val)) return val; p = JS_VALUE_GET_PTR(val); if (p->len == 0 && p->is_wide_char != 0) { ret = JS_NULL; } else { ret = JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p)); } JS_FreeValue(ctx, val); return ret; } static const JSCFunctionListEntry js_symbol_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_symbol_toString ), JS_CFUNC_DEF("valueOf", 0, js_symbol_valueOf ), // XXX: should have writable: false JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_symbol_valueOf ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Symbol", JS_PROP_CONFIGURABLE ), JS_CGETSET_DEF("description", js_symbol_get_description, NULL ), }; /* global object */ /* ============================================================================ * Cell Script Native Global Functions * ============================================================================ * These functions implement the core Cell script primitives: * - text: string conversion and manipulation * - number: number conversion and math utilities * - array: array creation and manipulation * - object: object creation and manipulation * - fn: function utilities * ============================================================================ */ /* ---------------------------------------------------------------------------- * number function and sub-functions * ---------------------------------------------------------------------------- */ /* number(val, format) - convert to number */ static JSValue js_cell_number(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue val = argv[0]; int tag = JS_VALUE_GET_TAG(val); /* Handle boolean */ if (tag == JS_TAG_BOOL) { return JS_NewInt32(ctx, JS_VALUE_GET_BOOL(val) ? 1 : 0); } /* Handle number - return as-is */ if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { return JS_DupValue(ctx, val); } /* Handle string */ if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE) { const char *str = JS_ToCString(ctx, val); if (!str) return JS_EXCEPTION; JSValue result; /* Check for format argument */ if (argc > 1 && JS_VALUE_GET_TAG(argv[1]) == JS_TAG_INT) { /* Radix conversion */ int radix = JS_VALUE_GET_INT(argv[1]); if (radix < 2 || radix > 36) { JS_FreeCString(ctx, str); return JS_NULL; } char *endptr; long long n = strtoll(str, &endptr, radix); if (endptr == str || *endptr != '\0') { JS_FreeCString(ctx, str); return JS_NULL; } result = JS_NewInt64(ctx, n); } else if (argc > 1 && (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_STRING || JS_VALUE_GET_TAG(argv[1]) == JS_TAG_STRING_ROPE)) { /* Format string */ const char *format = JS_ToCString(ctx, argv[1]); if (!format) { JS_FreeCString(ctx, str); return JS_EXCEPTION; } char *clean = js_malloc(ctx, strlen(str) + 1); if (!clean) { JS_FreeCString(ctx, format); JS_FreeCString(ctx, str); return JS_EXCEPTION; } const char *p = str; char *q = clean; if (strcmp(format, "u") == 0) { /* underbar separator */ while (*p) { if (*p != '_') *q++ = *p; p++; } } else if (strcmp(format, "d") == 0 || strcmp(format, "l") == 0) { /* comma separator */ while (*p) { if (*p != ',') *q++ = *p; p++; } } else if (strcmp(format, "s") == 0) { /* space separator */ while (*p) { if (*p != ' ') *q++ = *p; p++; } } else if (strcmp(format, "v") == 0) { /* European style: period separator, comma decimal */ while (*p) { if (*p == '.') { p++; continue; } if (*p == ',') { *q++ = '.'; p++; continue; } *q++ = *p++; } } else if (strcmp(format, "b") == 0) { *q = '\0'; char *endptr; long long n = strtoll(str, &endptr, 2); js_free(ctx, clean); JS_FreeCString(ctx, format); JS_FreeCString(ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64(ctx, n); } else if (strcmp(format, "o") == 0) { *q = '\0'; char *endptr; long long n = strtoll(str, &endptr, 8); js_free(ctx, clean); JS_FreeCString(ctx, format); JS_FreeCString(ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64(ctx, n); } else if (strcmp(format, "h") == 0) { *q = '\0'; char *endptr; long long n = strtoll(str, &endptr, 16); js_free(ctx, clean); JS_FreeCString(ctx, format); JS_FreeCString(ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64(ctx, n); } else if (strcmp(format, "t") == 0) { *q = '\0'; char *endptr; long long n = strtoll(str, &endptr, 32); js_free(ctx, clean); JS_FreeCString(ctx, format); JS_FreeCString(ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64(ctx, n); } else if (strcmp(format, "j") == 0) { /* JavaScript style prefix */ js_free(ctx, clean); JS_FreeCString(ctx, format); int radix = 10; const char *start = str; if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { radix = 16; start = str + 2; } else if (str[0] == '0' && (str[1] == 'o' || str[1] == 'O')) { radix = 8; start = str + 2; } else if (str[0] == '0' && (str[1] == 'b' || str[1] == 'B')) { radix = 2; start = str + 2; } if (radix != 10) { char *endptr; long long n = strtoll(start, &endptr, radix); JS_FreeCString(ctx, str); if (endptr == start) return JS_NULL; return JS_NewInt64(ctx, n); } double d = strtod(str, NULL); JS_FreeCString(ctx, str); return JS_NewFloat64(ctx, d); } else { /* Unknown format, just copy */ strcpy(clean, str); q = clean + strlen(clean); } *q = '\0'; double d = strtod(clean, NULL); js_free(ctx, clean); JS_FreeCString(ctx, format); JS_FreeCString(ctx, str); if (isnan(d)) return JS_NULL; return JS_NewFloat64(ctx, d); } else { /* Default: parse as decimal */ char *endptr; double d = strtod(str, &endptr); JS_FreeCString(ctx, str); if (endptr == str || isnan(d)) return JS_NULL; result = JS_NewFloat64(ctx, d); } return result; } return JS_NULL; } /* number.whole(n) - truncate to integer */ static JSValue js_cell_number_whole(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; return JS_NewFloat64(ctx, trunc(d)); } /* number.fraction(n) - get fractional part */ static JSValue js_cell_number_fraction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; return JS_NewFloat64(ctx, d - trunc(d)); } /* number.floor(n, place) - floor with optional decimal place */ static JSValue js_cell_number_floor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull(argv[1])) { return JS_NewFloat64(ctx, floor(d)); } int place; if (JS_ToInt32(ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64(ctx, floor(d)); double mult = pow(10, place); return JS_NewFloat64(ctx, floor(d * mult) / mult); } /* number.ceiling(n, place) - ceiling with optional decimal place */ static JSValue js_cell_number_ceiling(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull(argv[1])) { return JS_NewFloat64(ctx, ceil(d)); } int place; if (JS_ToInt32(ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64(ctx, ceil(d)); double mult = pow(10, place); return JS_NewFloat64(ctx, ceil(d * mult) / mult); } /* number.abs(n) - absolute value */ static JSValue js_cell_number_abs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; return JS_NewFloat64(ctx, fabs(d)); } /* number.round(n, place) - round with optional decimal place */ static JSValue js_cell_number_round(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull(argv[1])) { return JS_NewFloat64(ctx, round(d)); } int place; if (JS_ToInt32(ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64(ctx, round(d)); double mult = pow(10, place); return JS_NewFloat64(ctx, round(d * mult) / mult); } /* number.sign(n) - return sign (-1, 0, 1) */ static JSValue js_cell_number_sign(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; if (d < 0) return JS_NewInt32(ctx, -1); if (d > 0) return JS_NewInt32(ctx, 1); return JS_NewInt32(ctx, 0); } /* number.trunc(n, place) - truncate with optional decimal place */ static JSValue js_cell_number_trunc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull(argv[1])) { return JS_NewFloat64(ctx, trunc(d)); } int place; if (JS_ToInt32(ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64(ctx, trunc(d)); double mult = pow(10, place); return JS_NewFloat64(ctx, trunc(d * mult) / mult); } /* number.min(...vals) - minimum value */ static JSValue js_cell_number_min(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc == 0) return JS_NULL; double result; if (JS_ToFloat64(ctx, &result, argv[0])) return JS_NULL; for (int i = 1; i < argc; i++) { double d; if (JS_ToFloat64(ctx, &d, argv[i])) return JS_NULL; if (d < result) result = d; } return JS_NewFloat64(ctx, result); } /* number.max(...vals) - maximum value */ static JSValue js_cell_number_max(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc == 0) return JS_NULL; double result; if (JS_ToFloat64(ctx, &result, argv[0])) return JS_NULL; for (int i = 1; i < argc; i++) { double d; if (JS_ToFloat64(ctx, &d, argv[i])) return JS_NULL; if (d > result) result = d; } return JS_NewFloat64(ctx, result); } /* number.remainder(dividend, divisor) - remainder after division */ static JSValue js_cell_number_remainder(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; double dividend, divisor; if (JS_ToFloat64(ctx, ÷nd, argv[0])) return JS_NULL; if (JS_ToFloat64(ctx, &divisor, argv[1])) return JS_NULL; if (divisor == 0) return JS_NULL; return JS_NewFloat64(ctx, dividend - (trunc(dividend / divisor) * divisor)); } /* ---------------------------------------------------------------------------- * text function and sub-functions * ---------------------------------------------------------------------------- */ /* Helper: convert number to string with radix */ static JSValue js_cell_number_to_radix_string(JSContext *ctx, double num, int radix) { if (radix < 2 || radix > 36) return JS_NULL; static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; char buf[70]; int len = 0; int negative = 0; int64_t n = (int64_t)trunc(num); if (n < 0) { negative = 1; n = -n; } if (n == 0) { buf[len++] = '0'; } else { while (n > 0) { buf[len++] = digits[n % radix]; n /= radix; } } if (negative) { buf[len++] = '-'; } /* Reverse the string */ char result[72]; int j = 0; for (int i = len - 1; i >= 0; i--) { result[j++] = buf[i]; } result[j] = '\0'; return JS_NewString(ctx, result); } /* Helper: add separator every n digits from right */ static char *add_separator(JSContext *ctx, const char *str, char sep, int n) { if (n <= 0) { char *result = js_malloc(ctx, strlen(str) + 1); if (result) strcpy(result, str); return result; } int negative = (str[0] == '-'); const char *start = negative ? str + 1 : str; /* Find decimal point */ const char *decimal = strchr(start, '.'); int int_len = decimal ? (int)(decimal - start) : (int)strlen(start); int num_seps = (int_len - 1) / n; int result_len = strlen(str) + num_seps + 1; char *result = js_malloc(ctx, result_len); if (!result) return NULL; char *q = result; if (negative) *q++ = '-'; int count = int_len % n; if (count == 0) count = n; for (int i = 0; i < int_len; i++) { if (i > 0 && count == 0) { *q++ = sep; count = n; } *q++ = start[i]; count--; } if (decimal) { strcpy(q, decimal); } else { *q = '\0'; } return result; } /* Helper: format number with format string */ static JSValue js_cell_format_number(JSContext *ctx, double num, const char *format) { int separation = 0; char style = '\0'; int places = 0; int i = 0; /* Parse separation digit */ if (format[i] >= '0' && format[i] <= '9') { separation = format[i] - '0'; i++; } /* Parse style letter */ if (format[i]) { style = format[i]; i++; } else { return JS_NULL; } /* Parse places digits */ if (format[i] >= '0' && format[i] <= '9') { places = format[i] - '0'; i++; if (format[i] >= '0' && format[i] <= '9') { places = places * 10 + (format[i] - '0'); i++; } } /* Invalid if more characters */ if (format[i] != '\0') return JS_NULL; char buf[128]; char *result_str = NULL; switch (style) { case 'e': { /* Exponential */ if (places > 0) snprintf(buf, sizeof(buf), "%.*e", places, num); else snprintf(buf, sizeof(buf), "%e", num); return JS_NewString(ctx, buf); } case 'n': { /* Number - scientific for extreme values */ if (fabs(num) >= 1e21 || (fabs(num) < 1e-6 && num != 0)) { snprintf(buf, sizeof(buf), "%e", num); } else if (places > 0) { snprintf(buf, sizeof(buf), "%.*f", places, num); } else { snprintf(buf, sizeof(buf), "%g", num); } return JS_NewString(ctx, buf); } case 's': { /* Space separated */ if (separation == 0) separation = 3; snprintf(buf, sizeof(buf), "%.*f", places, num); result_str = add_separator(ctx, buf, ' ', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString(ctx, result_str); js_free(ctx, result_str); return ret; } case 'u': { /* Underbar separated */ snprintf(buf, sizeof(buf), "%.*f", places, num); if (separation > 0) { result_str = add_separator(ctx, buf, '_', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString(ctx, result_str); js_free(ctx, result_str); return ret; } return JS_NewString(ctx, buf); } case 'd': case 'l': { /* Decimal/locale with comma separator */ if (separation == 0) separation = 3; if (places == 0 && style == 'd') places = 2; snprintf(buf, sizeof(buf), "%.*f", places, num); result_str = add_separator(ctx, buf, ',', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString(ctx, result_str); js_free(ctx, result_str); return ret; } case 'v': { /* European style: comma decimal, period separator */ snprintf(buf, sizeof(buf), "%.*f", places, num); /* Replace . with , */ for (char *p = buf; *p; p++) { if (*p == '.') *p = ','; } if (separation > 0) { result_str = add_separator(ctx, buf, '.', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString(ctx, result_str); js_free(ctx, result_str); return ret; } return JS_NewString(ctx, buf); } case 'i': { /* Integer base 10 */ if (places == 0) places = 1; int64_t n = (int64_t)trunc(num); int neg = n < 0; if (neg) n = -n; snprintf(buf, sizeof(buf), "%lld", (long long)n); int len = strlen(buf); /* Pad with zeros */ if (len < places) { memmove(buf + (places - len), buf, len + 1); memset(buf, '0', places - len); } if (separation > 0) { result_str = add_separator(ctx, buf, '_', separation); if (!result_str) return JS_EXCEPTION; if (neg) { char *final = js_malloc(ctx, strlen(result_str) + 2); if (!final) { js_free(ctx, result_str); return JS_EXCEPTION; } final[0] = '-'; strcpy(final + 1, result_str); js_free(ctx, result_str); JSValue ret = JS_NewString(ctx, final); js_free(ctx, final); return ret; } JSValue ret = JS_NewString(ctx, result_str); js_free(ctx, result_str); return ret; } if (neg) { memmove(buf + 1, buf, strlen(buf) + 1); buf[0] = '-'; } return JS_NewString(ctx, buf); } case 'b': { /* Binary */ if (places == 0) places = 1; return js_cell_number_to_radix_string(ctx, num, 2); } case 'o': { /* Octal */ if (places == 0) places = 1; int64_t n = (int64_t)trunc(num); snprintf(buf, sizeof(buf), "%llo", (long long)(n < 0 ? -n : n)); /* Uppercase and pad */ for (char *p = buf; *p; p++) *p = toupper(*p); int len = strlen(buf); if (len < places) { memmove(buf + (places - len), buf, len + 1); memset(buf, '0', places - len); } if (n < 0) { memmove(buf + 1, buf, strlen(buf) + 1); buf[0] = '-'; } return JS_NewString(ctx, buf); } case 'h': { /* Hexadecimal */ if (places == 0) places = 1; int64_t n = (int64_t)trunc(num); snprintf(buf, sizeof(buf), "%llX", (long long)(n < 0 ? -n : n)); int len = strlen(buf); if (len < places) { memmove(buf + (places - len), buf, len + 1); memset(buf, '0', places - len); } if (n < 0) { memmove(buf + 1, buf, strlen(buf) + 1); buf[0] = '-'; } return JS_NewString(ctx, buf); } case 't': { /* Base32 */ if (places == 0) places = 1; return js_cell_number_to_radix_string(ctx, num, 32); } } return JS_NULL; } /* Forward declaration for blob helper */ static blob *js_get_blob(JSContext *ctx, JSValueConst val); /* modulo(dividend, divisor) - result has sign of divisor */ static JSValue js_cell_modulo(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; double dividend, divisor; if (JS_ToFloat64(ctx, ÷nd, argv[0])) return JS_NULL; if (JS_ToFloat64(ctx, &divisor, argv[1])) return JS_NULL; /* If either operand is NaN, return null */ if (isnan(dividend) || isnan(divisor)) return JS_NULL; /* If divisor is 0, return null */ if (divisor == 0) return JS_NULL; /* If dividend is 0, return 0 */ if (dividend == 0) return JS_NewFloat64(ctx, 0.0); /* modulo = dividend - (divisor * floor(dividend / divisor)) */ double result = dividend - (divisor * floor(dividend / divisor)); return JS_NewFloat64(ctx, result); } /* not(bool) - negate a boolean value */ static JSValue js_cell_not(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; if (!JS_IsBool(argv[0])) return JS_NULL; return JS_NewBool(ctx, !JS_ToBool(ctx, argv[0])); } /* neg(number) - negate a number */ static JSValue js_cell_neg(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; double num; if (JS_ToFloat64(ctx, &num, argv[0])) return JS_NULL; if (isnan(num)) return JS_NULL; return JS_NewFloat64(ctx, -num); } /* character(value) - get character from text or codepoint */ static JSValue js_cell_character(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NewString(ctx, ""); JSValue arg = argv[0]; int tag = JS_VALUE_GET_TAG(arg); /* Handle string - return first character */ if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE) { JSString *p = JS_VALUE_GET_STRING(arg); if (p->len == 0) return JS_NewString(ctx, ""); /* Return first character (handle UTF-16 surrogate pairs) */ if (p->is_wide_char) { uint32_t c = p->u.str16[0]; if (is_hi_surrogate(c) && p->len > 1 && is_lo_surrogate(p->u.str16[1])) { /* Surrogate pair - return both code units */ return js_sub_string(ctx, p, 0, 2); } return js_sub_string(ctx, p, 0, 1); } else { return js_sub_string(ctx, p, 0, 1); } } /* Handle integer - return character from codepoint */ if (tag == JS_TAG_INT) { int32_t val = JS_VALUE_GET_INT(arg); if (val < 0 || val > 0x10FFFF) return JS_NewString(ctx, ""); uint32_t codepoint = (uint32_t)val; if (codepoint < 0x80) { char buf[2] = { (char)codepoint, '\0' }; return JS_NewString(ctx, buf); } else if (codepoint <= 0xFFFF) { uint16_t buf[2] = { (uint16_t)codepoint, 0 }; return js_new_string16_len(ctx, buf, 1); } else { /* Encode as surrogate pair */ codepoint -= 0x10000; uint16_t buf[3]; buf[0] = 0xD800 + (codepoint >> 10); buf[1] = 0xDC00 + (codepoint & 0x3FF); buf[2] = 0; return js_new_string16_len(ctx, buf, 2); } } /* Handle float - convert to integer if non-negative and within range */ if (tag == JS_TAG_FLOAT64) { double d = JS_VALUE_GET_FLOAT64(arg); if (isnan(d) || d < 0 || d > 0x10FFFF || d != trunc(d)) return JS_NewString(ctx, ""); uint32_t codepoint = (uint32_t)d; if (codepoint < 0x80) { char buf[2] = { (char)codepoint, '\0' }; return JS_NewString(ctx, buf); } else if (codepoint <= 0xFFFF) { uint16_t buf[2] = { (uint16_t)codepoint, 0 }; return js_new_string16_len(ctx, buf, 1); } else { /* Encode as surrogate pair */ codepoint -= 0x10000; uint16_t buf[3]; buf[0] = 0xD800 + (codepoint >> 10); buf[1] = 0xDC00 + (codepoint & 0x3FF); buf[2] = 0; return js_new_string16_len(ctx, buf, 2); } } return JS_NewString(ctx, ""); } /* text(arg, format) - main text function */ static JSValue js_cell_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue arg = argv[0]; int tag = JS_VALUE_GET_TAG(arg); /* Handle string */ if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE) { if (argc == 1) return JS_DupValue(ctx, arg); JSString *p = JS_VALUE_GET_STRING(arg); int len = p->len; /* text(string, from) - substring from index to end */ /* text(string, from, to) - substring */ if (argc >= 2 && (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_INT || JS_VALUE_GET_TAG(argv[1]) == JS_TAG_FLOAT64)) { int from; if (JS_ToInt32(ctx, &from, argv[1])) return JS_NULL; /* Adjust negative index */ if (from < 0) from += len; if (from < 0) from = 0; if (from > len) from = len; int to = len; /* Default: to end of string */ if (argc >= 3) { if (JS_ToInt32(ctx, &to, argv[2])) return JS_NULL; /* Adjust negative index */ if (to < 0) to += len; if (to < 0) to = 0; if (to > len) to = len; } if (from > to) return JS_NULL; return js_sub_string(ctx, p, from, to); } return JS_DupValue(ctx, arg); } /* Handle blob - convert to text representation */ blob *bd = js_get_blob(ctx, arg); if (bd) { if (!bd->is_stone) return JS_ThrowTypeError(ctx, "text: blob must be stone"); char format = '\0'; if (argc > 1) { const char *fmt = JS_ToCString(ctx, argv[1]); if (fmt) { format = fmt[0]; JS_FreeCString(ctx, fmt); } } size_t byte_len = (bd->length + 7) / 8; const uint8_t *data = bd->data; if (format == 'h') { /* Hexadecimal encoding */ static const char hex[] = "0123456789abcdef"; char *result = js_malloc(ctx, byte_len * 2 + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < byte_len; i++) { result[i * 2] = hex[(data[i] >> 4) & 0xF]; result[i * 2 + 1] = hex[data[i] & 0xF]; } result[byte_len * 2] = '\0'; JSValue ret = JS_NewString(ctx, result); js_free(ctx, result); return ret; } else if (format == 'b') { /* Binary encoding */ char *result = js_malloc(ctx, bd->length + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < bd->length; i++) { size_t byte_idx = i / 8; size_t bit_idx = i % 8; result[i] = (data[byte_idx] & (1 << bit_idx)) ? '1' : '0'; } result[bd->length] = '\0'; JSValue ret = JS_NewString(ctx, result); js_free(ctx, result); return ret; } else if (format == 'o') { /* Octal encoding - 3 bits at a time */ size_t octal_len = (bd->length + 2) / 3; char *result = js_malloc(ctx, octal_len + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < octal_len; i++) { int val = 0; for (int j = 0; j < 3; j++) { size_t bit_pos = i * 3 + j; if (bit_pos < bd->length) { size_t byte_idx = bit_pos / 8; size_t bit_idx = bit_pos % 8; if (data[byte_idx] & (1 << bit_idx)) val |= (1 << j); } } result[i] = '0' + val; } result[octal_len] = '\0'; JSValue ret = JS_NewString(ctx, result); js_free(ctx, result); return ret; } else if (format == 't') { /* Base32 encoding (RFC 4648) */ static const char b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; size_t b32_len = (bd->length + 4) / 5; char *result = js_malloc(ctx, b32_len + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < b32_len; i++) { int val = 0; for (int j = 0; j < 5; j++) { size_t bit_pos = i * 5 + j; if (bit_pos < bd->length) { size_t byte_idx = bit_pos / 8; size_t bit_idx = bit_pos % 8; if (data[byte_idx] & (1 << bit_idx)) val |= (1 << j); } } result[i] = b32[val]; } result[b32_len] = '\0'; JSValue ret = JS_NewString(ctx, result); js_free(ctx, result); return ret; } else { /* Default: UTF-8 (treat bytes as UTF-8 text) */ if (bd->length % 8 != 0) return JS_ThrowTypeError(ctx, "text: blob not byte-aligned for UTF-8"); return JS_NewStringLen(ctx, (const char *)data, byte_len); } } /* Handle number */ if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { double num; if (JS_ToFloat64(ctx, &num, arg)) return JS_EXCEPTION; if (argc > 1) { /* Check for radix (number) or format (string) */ if (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_INT) { int radix = JS_VALUE_GET_INT(argv[1]); return js_cell_number_to_radix_string(ctx, num, radix); } if (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_STRING || JS_VALUE_GET_TAG(argv[1]) == JS_TAG_STRING_ROPE) { const char *format = JS_ToCString(ctx, argv[1]); if (!format) return JS_EXCEPTION; JSValue result = js_cell_format_number(ctx, num, format); JS_FreeCString(ctx, format); return result; } } return js_cell_number_to_radix_string(ctx, num, 10); } /* Handle array */ if (JS_IsArray(ctx, arg)) { int64_t len; if (js_get_length64(ctx, &len, arg)) return JS_EXCEPTION; const char *separator = ""; if (argc > 1 && (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_STRING || JS_VALUE_GET_TAG(argv[1]) == JS_TAG_STRING_ROPE)) { separator = JS_ToCString(ctx, argv[1]); if (!separator) return JS_EXCEPTION; } StringBuffer b_s, *b = &b_s; string_buffer_init(ctx, b, 0); for (int64_t i = 0; i < len; i++) { if (i > 0 && separator[0]) { if (string_buffer_puts8(b, separator)) { string_buffer_free(b); if (argc > 1) JS_FreeCString(ctx, separator); return JS_EXCEPTION; } } JSValue item = JS_GetPropertyInt64(ctx, arg, i); if (JS_IsException(item)) { string_buffer_free(b); if (argc > 1) JS_FreeCString(ctx, separator); return JS_EXCEPTION; } if (!JS_VALUE_IS_TEXT(item)) return JS_ThrowInternalError(ctx, "Array must be made of strings."); if (string_buffer_concat_value_free(b, item)) { string_buffer_free(b); if (argc > 1) JS_FreeCString(ctx, separator); return JS_EXCEPTION; } } if (argc > 1) JS_FreeCString(ctx, separator); return string_buffer_end(b); } /* Handle function - return source or native stub */ if (JS_IsFunction(ctx, arg)) { JSObject *p = JS_VALUE_GET_OBJ(arg); if (js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b = p->u.func.function_bytecode; if (b->has_debug && b->debug.source) { return JS_NewStringLen(ctx, b->debug.source, b->debug.source_len); } } /* Native function - generate stub */ const char *pref = "function "; const char *suff = "() {\n [native code]\n}"; const char *name = NULL; JSObject *fp = JS_VALUE_GET_OBJ(arg); if (js_class_has_bytecode(fp->class_id)) { JSFunctionBytecode *fb = fp->u.func.function_bytecode; name = JS_AtomToCString(ctx, fb->func_name); } if (!name) name = ""; size_t plen = strlen(pref); size_t nlen = strlen(name); size_t slen = strlen(suff); char *result = js_malloc(ctx, plen + nlen + slen + 1); if (!result) { if (name[0]) JS_FreeCString(ctx, name); return JS_EXCEPTION; } memcpy(result, pref, plen); memcpy(result + plen, name, nlen); memcpy(result + plen + nlen, suff, slen + 1); JSValue ret = JS_NewString(ctx, result); js_free(ctx, result); if (name[0]) JS_FreeCString(ctx, name); return ret; } /* Handle null */ // if (JS_IsNull(arg)) // return JS_NULL; if (JS_IsObject(arg)) return JS_NewString(ctx, "[object]"); return JS_ThrowInternalError(ctx, "Could not convert to text. Tag is %d", JS_VALUE_GET_TAG(arg)); } /* text.lower(str) - convert to lowercase */ static JSValue js_cell_text_lower(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; if (!JS_VALUE_IS_TEXT(argv[0])) return JS_NULL; JSString *p = JS_VALUE_GET_STRING(argv[0]); StringBuffer b_s, *b = &b_s; string_buffer_init(ctx, b, p->len); for (int i = 0; i < p->len; i++) { uint32_t c = string_get(p, i); if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; if (string_buffer_putc(b, c)) { string_buffer_free(b); return JS_EXCEPTION; } } return string_buffer_end(b); } /* text.upper(str) - convert to uppercase */ static JSValue js_cell_text_upper(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; if (!JS_VALUE_IS_TEXT(argv[0])) return JS_NULL; JSString *p = JS_VALUE_GET_STRING(argv[0]); StringBuffer b_s, *b = &b_s; string_buffer_init(ctx, b, p->len); for (int i = 0; i < p->len; i++) { uint32_t c = string_get(p, i); if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; if (string_buffer_putc(b, c)) { string_buffer_free(b); return JS_EXCEPTION; } } return string_buffer_end(b); } /* text.trim(str, reject) - trim whitespace or custom characters */ static JSValue js_cell_text_trim(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; int tag = JS_VALUE_GET_TAG(argv[0]); if (tag != JS_TAG_STRING && tag != JS_TAG_STRING_ROPE) return JS_NULL; JSValue str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; JSString *p = JS_VALUE_GET_STRING(str); int start = 0; int end = p->len; if (argc > 1 && !JS_IsNull(argv[1])) { /* Custom trim with reject characters */ const char *reject = JS_ToCString(ctx, argv[1]); if (!reject) { /* str removed - arg not owned */ return JS_EXCEPTION; } size_t reject_len = strlen(reject); while (start < end) { uint32_t c = string_get(p, start); int found = 0; for (size_t i = 0; i < reject_len; i++) { if (c == (uint8_t)reject[i]) { found = 1; break; } } if (!found) break; start++; } while (end > start) { uint32_t c = string_get(p, end - 1); int found = 0; for (size_t i = 0; i < reject_len; i++) { if (c == (uint8_t)reject[i]) { found = 1; break; } } if (!found) break; end--; } JS_FreeCString(ctx, reject); } else { /* Default: trim whitespace */ while (start < end && lre_is_space(string_get(p, start))) start++; while (end > start && lre_is_space(string_get(p, end - 1))) end--; } JSValue result = js_sub_string(ctx, p, start, end); /* str removed - arg not owned */ return result; } /* text.codepoint(str) - get first codepoint */ static JSValue js_cell_text_codepoint(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; int tag = JS_VALUE_GET_TAG(argv[0]); if (tag != JS_TAG_STRING && tag != JS_TAG_STRING_ROPE) return JS_NULL; JSValue str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; JSString *p = JS_VALUE_GET_STRING(str); if (p->len == 0) { /* str removed - arg not owned */ return JS_NULL; } uint32_t c = string_get(p, 0); /* Handle surrogate pairs */ if (c >= 0xD800 && c <= 0xDBFF && p->len > 1) { uint32_t c2 = string_get(p, 1); if (c2 >= 0xDC00 && c2 <= 0xDFFF) { c = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00); } } /* str removed - arg not owned */ return JS_NewInt32(ctx, c); } /* Helpers (C, not C++). Put these above js_cell_text_replace in the same C file. */ static int sb_concat_value_to_string_free(JSContext *ctx, StringBuffer *b, JSValue v) { JSValue s = JS_ToString(ctx, v); JS_FreeValue(ctx, v); if (JS_IsException(s)) return -1; if (string_buffer_concat_value_free(b, s)) return -1; return 0; } /* Build replacement for a match at `found`. * - If replacement is a function: call it as (match_text, found) * - Else if replacement exists: duplicate it * - Else: empty string * Returns JS_EXCEPTION on error, JS_NULL if callback returned null, or any JSValue. * This function CONSUMES match_val if it calls a function (it will free it via args cleanup), * otherwise it will free match_val before returning. */ static JSValue make_replacement(JSContext *ctx, int argc, JSValueConst *argv, int found, JSValue match_val) { JSValue rep; if (argc > 2 && JS_IsFunction(ctx, argv[2])) { JSValue args[2]; args[0] = match_val; args[1] = JS_NewInt32(ctx, found); rep = JS_Call(ctx, argv[2], JS_NULL, 2, args); JS_FreeValue(ctx, args[0]); JS_FreeValue(ctx, args[1]); return rep; } JS_FreeValue(ctx, match_val); if (argc > 2) return JS_DupValue(ctx, argv[2]); return JS_AtomToString(ctx, JS_ATOM_empty_string); } static int JS_IsRegExp(JSContext *ctx, JSValueConst v) { if (!JS_IsObject(v)) return 0; JSValue exec = JS_GetPropertyStr(ctx, v, "exec"); if (JS_IsException(exec)) return -1; int ok = JS_IsFunction(ctx, exec); JS_FreeValue(ctx, exec); return ok; } /* text.replace(text, target, replacement, limit) * * Return a new text in which the target is replaced by the replacement. * * target: string (pattern support not implemented here; non-string => null) * replacement: string or function(match_text, start_pos) -> string|null * limit: max number of replacements (default unlimited). Limit includes null matches. * * Empty target semantics: * Replace at every boundary: before first char, between chars, after last char. * Example: replace("abc", "", "-") => "-a-b-c-" * Boundaries count toward limit even if replacement returns null. */ static JSValue js_cell_text_replace(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; int tag_text = JS_VALUE_GET_TAG(argv[0]); if (tag_text != JS_TAG_STRING && tag_text != JS_TAG_STRING_ROPE) return JS_NULL; int target_is_regex = 0; { int tag_tgt = JS_VALUE_GET_TAG(argv[1]); if (tag_tgt == JS_TAG_STRING || tag_tgt == JS_TAG_STRING_ROPE) { target_is_regex = 0; } else if (JS_IsObject(argv[1]) && JS_IsRegExp(ctx, argv[1])) { target_is_regex = 1; } else { return JS_NULL; } } if (!JS_VALUE_IS_TEXT(argv[0])) return JS_ThrowInternalError(ctx, "Replace must have text in arg0."); JSString *sp = JS_VALUE_GET_STRING(argv[0]); int len = (int)sp->len; int32_t limit = -1; if (argc > 3 && !JS_IsNull(argv[3])) { if (JS_ToInt32(ctx, &limit, argv[3])) { return JS_NULL; } if (limit < 0) limit = -1; } StringBuffer b_s, *b = &b_s; string_buffer_init(ctx, b, len); if (!target_is_regex) { if (!JS_VALUE_IS_TEXT(argv[1])) return JS_ThrowInternalError(ctx, "Second arg of replace must be pattern or text."); JSString *tp = JS_VALUE_GET_STRING(argv[1]); int t_len = (int)tp->len; if (t_len == 0) { int32_t count = 0; for (int boundary = 0; boundary <= len; boundary++) { if (limit >= 0 && count >= limit) break; JSValue match = JS_AtomToString(ctx, JS_ATOM_empty_string); if (JS_IsException(match)) goto fail_str_target; JSValue rep = make_replacement(ctx, argc, argv, boundary, match); if (JS_IsException(rep)) goto fail_str_target; count++; if (!JS_IsNull(rep)) { if (sb_concat_value_to_string_free(ctx, b, rep) < 0) goto fail_str_target; } else { JS_FreeValue(ctx, rep); } if (boundary < len) { JSValue ch = js_sub_string(ctx, sp, boundary, boundary + 1); if (JS_IsException(ch)) goto fail_str_target; if (string_buffer_concat_value_free(b, ch)) goto fail_str_target; } } return string_buffer_end(b); } int pos = 0; int32_t count = 0; while (pos <= len - t_len && (limit < 0 || count < limit)) { int found = -1; for (int i = pos; i <= len - t_len; i++) { if (!string_cmp(sp, tp, i, 0, t_len)) { found = i; break; } } if (found < 0) break; if (found > pos) { JSValue sub = js_sub_string(ctx, sp, pos, found); if (JS_IsException(sub)) goto fail_str_target; if (string_buffer_concat_value_free(b, sub)) goto fail_str_target; } JSValue match = js_sub_string(ctx, sp, found, found + t_len); if (JS_IsException(match)) goto fail_str_target; JSValue rep = make_replacement(ctx, argc, argv, found, match); if (JS_IsException(rep)) goto fail_str_target; count++; if (!JS_IsNull(rep)) { if (sb_concat_value_to_string_free(ctx, b, rep) < 0) goto fail_str_target; } else { JS_FreeValue(ctx, rep); } pos = found + t_len; } if (pos < len) { JSValue sub = js_sub_string(ctx, sp, pos, len); if (JS_IsException(sub)) goto fail_str_target; if (string_buffer_concat_value_free(b, sub)) goto fail_str_target; } return string_buffer_end(b); fail_str_target: string_buffer_free(b); return JS_EXCEPTION; } /* Regex target */ JSValue rx = argv[1]; JSValue orig_last_index = JS_GetPropertyStr(ctx, rx, "lastIndex"); if (JS_IsException(orig_last_index)) goto fail_rx; int have_orig_last_index = 1; int pos = 0; int32_t count = 0; while (pos <= len && (limit < 0 || count < limit)) { if (JS_SetPropertyStr(ctx, rx, "lastIndex", JS_NewInt32(ctx, 0)) < 0) goto fail_rx; JSValue sub_str = js_sub_string(ctx, sp, pos, len); if (JS_IsException(sub_str)) goto fail_rx; JSValue exec_res = JS_Invoke(ctx, rx, JS_ATOM_exec, 1, (JSValueConst *)&sub_str); JS_FreeValue(ctx, sub_str); if (JS_IsException(exec_res)) goto fail_rx; if (JS_IsNull(exec_res)) { JS_FreeValue(ctx, exec_res); break; } JSValue idx_val = JS_GetPropertyStr(ctx, exec_res, "index"); if (JS_IsException(idx_val)) { JS_FreeValue(ctx, exec_res); goto fail_rx; } int32_t local_index = 0; if (JS_ToInt32(ctx, &local_index, idx_val)) { JS_FreeValue(ctx, idx_val); JS_FreeValue(ctx, exec_res); goto fail_rx; } JS_FreeValue(ctx, idx_val); if (local_index < 0) local_index = 0; int found = pos + local_index; if (found < pos) found = pos; if (found > len) { JS_FreeValue(ctx, exec_res); break; } JSValue match = JS_GetPropertyUint32(ctx, exec_res, 0); JS_FreeValue(ctx, exec_res); if (JS_IsException(match)) goto fail_rx; int match_len = 0; { JSValue mstr = JS_ToString(ctx, match); if (JS_IsException(mstr)) goto fail_rx; JSString *mp = JS_VALUE_GET_STRING(mstr); match_len = (int)mp->len; JS_FreeValue(ctx, mstr); } if (found > pos) { JSValue prefix = js_sub_string(ctx, sp, pos, found); if (JS_IsException(prefix)) goto fail_rx; if (string_buffer_concat_value_free(b, prefix)) goto fail_rx; } JSValue rep = make_replacement(ctx, argc, argv, found, match); if (JS_IsException(rep)) goto fail_rx; count++; if (!JS_IsNull(rep)) { if (sb_concat_value_to_string_free(ctx, b, rep) < 0) goto fail_rx; } else { JS_FreeValue(ctx, rep); } pos = found + match_len; if (match_len == 0) { if (pos < len) pos++; else break; } } if (pos < len) { JSValue tail = js_sub_string(ctx, sp, pos, len); if (JS_IsException(tail)) goto fail_rx; if (string_buffer_concat_value_free(b, tail)) goto fail_rx; } if (have_orig_last_index) JS_SetPropertyStr(ctx, rx, "lastIndex", orig_last_index); return string_buffer_end(b); fail_rx: string_buffer_free(b); if (!JS_IsNull(orig_last_index) && !JS_IsException(orig_last_index)) { JS_SetPropertyStr(ctx, rx, "lastIndex", orig_last_index); } else { JS_FreeValue(ctx, orig_last_index); } return JS_EXCEPTION; } /* text.search(str, target, from) - find substring or regex match */ static JSValue js_cell_text_search(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; int tag1 = JS_VALUE_GET_TAG(argv[0]); if (tag1 != JS_TAG_STRING && tag1 != JS_TAG_STRING_ROPE) return JS_NULL; int target_is_regex = 0; int tag2 = JS_VALUE_GET_TAG(argv[1]); if (tag2 == JS_TAG_STRING || tag2 == JS_TAG_STRING_ROPE) { target_is_regex = 0; } else if (JS_IsObject(argv[1]) && JS_IsRegExp(ctx, argv[1])) { target_is_regex = 1; } else { return JS_NULL; } JSValue str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; JSString *p = JS_VALUE_GET_STRING(str); int len = (int)p->len; int from = 0; if (argc > 2 && !JS_IsNull(argv[2])) { if (JS_ToInt32(ctx, &from, argv[2])) { /* str removed - arg not owned */ return JS_NULL; } if (from < 0) from += len; if (from < 0) from = 0; } if (from > len) { /* str removed - arg not owned */ return JS_NULL; } if (!target_is_regex) { JSValue target = JS_ToString(ctx, argv[1]); if (JS_IsException(target)) { /* str removed - arg not owned */ return target; } JSString *t = JS_VALUE_GET_STRING(target); int t_len = (int)t->len; int result = -1; if (len >= t_len) { for (int i = from; i <= len - t_len; i++) { if (!string_cmp(p, t, i, 0, t_len)) { result = i; break; } } } /* str removed - arg not owned */ JS_FreeValue(ctx, target); if (result == -1) return JS_NULL; return JS_NewInt32(ctx, result); } /* Regex target */ JSValue rx = argv[1]; JSValue orig_last_index = JS_GetPropertyStr(ctx, rx, "lastIndex"); if (JS_IsException(orig_last_index)) { /* str removed - arg not owned */ return JS_EXCEPTION; } int have_orig_last_index = 1; if (JS_SetPropertyStr(ctx, rx, "lastIndex", JS_NewInt32(ctx, 0)) < 0) goto fail_rx_search; JSValue sub_str = js_sub_string(ctx, p, from, len); if (JS_IsException(sub_str)) goto fail_rx_search; JSValue exec_res = JS_Invoke(ctx, rx, JS_ATOM_exec, 1, (JSValueConst *)&sub_str); JS_FreeValue(ctx, sub_str); if (JS_IsException(exec_res)) goto fail_rx_search; if (JS_IsNull(exec_res)) { JS_FreeValue(ctx, exec_res); if (have_orig_last_index) JS_SetPropertyStr(ctx, rx, "lastIndex", orig_last_index); /* str removed - arg not owned */ return JS_NULL; } JSValue idx_val = JS_GetPropertyStr(ctx, exec_res, "index"); if (JS_IsException(idx_val)) { JS_FreeValue(ctx, exec_res); goto fail_rx_search; } int32_t local_index = 0; if (JS_ToInt32(ctx, &local_index, idx_val)) { JS_FreeValue(ctx, idx_val); JS_FreeValue(ctx, exec_res); goto fail_rx_search; } JS_FreeValue(ctx, idx_val); JS_FreeValue(ctx, exec_res); if (local_index < 0) local_index = 0; if (have_orig_last_index) JS_SetPropertyStr(ctx, rx, "lastIndex", orig_last_index); /* str removed - arg not owned */ return JS_NewInt32(ctx, from + local_index); fail_rx_search: if (!JS_IsNull(orig_last_index) && !JS_IsException(orig_last_index)) { JS_SetPropertyStr(ctx, rx, "lastIndex", orig_last_index); } else { JS_FreeValue(ctx, orig_last_index); } /* str removed - arg not owned */ return JS_EXCEPTION; } static inline uint16_t js_str_get(JSString *s, int idx) { return s->is_wide_char ? s->u.str16[idx] : s->u.str8[idx]; } static int js_str_find_range(JSString *hay, int from, int to, JSString *needle) { int nlen = (int)needle->len; int hlen = (int)hay->len; if (from < 0) from = 0; if (to < 0) to = 0; if (to > hlen) to = hlen; if (from > to) return -1; if (nlen == 0) return from; if (nlen > (to - from)) return -1; int limit = to - nlen; for (int i = from; i <= limit; i++) { int j = 0; for (; j < nlen; j++) { if (js_str_get(hay, i + j) != js_str_get(needle, j)) break; } if (j == nlen) return i; } return -1; } /* text_extract(text, pattern, from?, to?) - extract match using regexp or literal text */ static JSValue js_cell_text_extract(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; JSValue str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return JS_EXCEPTION; JSString *p = JS_VALUE_GET_STRING(str); int len = (int)p->len; int from = 0; if (argc >= 3 && !JS_IsNull(argv[2])) { if (JS_ToInt32(ctx, &from, argv[2])) { /* str removed - arg not owned */ return JS_EXCEPTION; } if (from < 0) from += len; if (from < 0) from = 0; if (from > len) from = len; } int to = len; if (argc >= 4 && !JS_IsNull(argv[3])) { if (JS_ToInt32(ctx, &to, argv[3])) { /* str removed - arg not owned */ return JS_EXCEPTION; } if (to < 0) to += len; if (to < 0) to = 0; if (to > len) to = len; } if (from > to) { /* str removed - arg not owned */ return JS_NULL; } /* RegExp path */ if (js_is_regexp(ctx, argv[1])) { JSValue substr; if (from == 0 && to == len) { substr = JS_DupValue(ctx, str); } else { substr = js_sub_string(ctx, p, from, to); if (JS_IsException(substr)) { /* str removed - arg not owned */ return JS_EXCEPTION; } } JSValue exec_func = JS_GetPropertyStr(ctx, argv[1], "exec"); if (JS_IsException(exec_func)) { JS_FreeValue(ctx, substr); /* str removed - arg not owned */ return JS_EXCEPTION; } JSValue result = JS_Call(ctx, exec_func, argv[1], 1, &substr); JS_FreeValue(ctx, exec_func); JS_FreeValue(ctx, substr); /* str removed - arg not owned */ if (JS_IsException(result)) return JS_EXCEPTION; return result; } /* Literal text path */ JSValue needle_val = JS_ToString(ctx, argv[1]); if (JS_IsException(needle_val)) { /* str removed - arg not owned */ return JS_EXCEPTION; } JSString *needle = JS_VALUE_GET_STRING(needle_val); int pos = js_str_find_range(p, from, to, needle); JS_FreeValue(ctx, needle_val); if (pos < 0) { /* str removed - arg not owned */ return JS_NULL; } JSValue arr = JS_NewArray(ctx); if (JS_IsException(arr)) { /* str removed - arg not owned */ return JS_EXCEPTION; } JSValue match = js_sub_string(ctx, p, pos, pos + (int)needle->len); if (JS_IsException(match)) { JS_FreeValue(ctx, arr); /* str removed - arg not owned */ return JS_EXCEPTION; } JS_DefinePropertyValueUint32(ctx, arr, 0, match, JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, arr, "index", JS_NewInt32(ctx, pos), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, arr, "input", JS_DupValue(ctx, str), JS_PROP_C_W_E); /* str removed - arg not owned */ return arr; } /* ---------------------------------------------------------------------------- * array function and sub-functions * ---------------------------------------------------------------------------- */ /* array(arg, arg2, arg3, arg4) - main array function */ static JSValue js_cell_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue arg = argv[0]; int tag = JS_VALUE_GET_TAG(arg); /* array(number) - create array of size */ /* array(number, initial_value) - create array with initial values */ if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { double d; if (JS_ToFloat64(ctx, &d, arg)) return JS_NULL; if (d < 0) return JS_NULL; int64_t len = (int64_t)floor(d); JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; if (argc < 2 || JS_IsNull(argv[1])) { /* Just set length */ JS_SetPropertyStr(ctx, result, "length", JS_NewInt64(ctx, len > 100 ? 100 : len)); } else if (JS_IsFunction(ctx, argv[1])) { /* Fill with function results */ JSValueConst func = argv[1]; int arity = 0; JSValue len_val = JS_GetPropertyStr(ctx, func, "length"); if (!JS_IsException(len_val)) { JS_ToInt32(ctx, &arity, len_val); JS_FreeValue(ctx, len_val); } for (int64_t i = 0; i < len; i++) { JSValue args[1] = { JS_NewInt64(ctx, i) }; JSValue val = arity >= 1 ? JS_Call(ctx, func, JS_NULL, 1, args) : JS_Call(ctx, func, JS_NULL, 0, NULL); JS_FreeValue(ctx, args[0]); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetPropertyInt64(ctx, result, i, val); } } else { /* Fill with value */ for (int64_t i = 0; i < len; i++) { JS_SetPropertyInt64(ctx, result, i, JS_DupValue(ctx, argv[1])); } } return result; } /* array(array) - copy */ /* array(array, function) - map */ /* array(array, another_array) - concat */ /* array(array, from, to) - slice */ if (JS_IsArray(ctx, arg)) { int64_t len; if (js_get_length64(ctx, &len, arg)) return JS_EXCEPTION; if (argc < 2 || JS_IsNull(argv[1])) { /* Copy */ JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; for (int64_t i = 0; i < len; i++) { JSValue val = JS_GetPropertyInt64(ctx, arg, i); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetPropertyInt64(ctx, result, i, val); } return result; } if (JS_IsFunction(ctx, argv[1])) { /* Map */ JSValueConst func = argv[1]; int reverse = argc > 2 && JS_ToBool(ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; if (reverse) { for (int64_t i = len - 1; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arg, i); if (JS_IsException(item)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JSValue args[2] = { item, JS_NewInt64(ctx, i) }; JSValue val = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, args[0]); JS_FreeValue(ctx, args[1]); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } if (!JS_IsNull(exit_val) && js_strict_eq(ctx, val, exit_val)) { JS_FreeValue(ctx, val); break; } JS_SetPropertyInt64(ctx, result, i, val); } } else { int64_t out_idx = 0; for (int64_t i = 0; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arg, i); if (JS_IsException(item)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JSValue args[2] = { item, JS_NewInt64(ctx, i) }; JSValue val = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, args[0]); JS_FreeValue(ctx, args[1]); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } if (!JS_IsNull(exit_val) && js_strict_eq(ctx, val, exit_val)) { JS_FreeValue(ctx, val); break; } JS_SetPropertyInt64(ctx, result, out_idx++, val); } } return result; } if (JS_IsArray(ctx, argv[1])) { /* Concat */ int64_t len2; if (js_get_length64(ctx, &len2, argv[1])) return JS_EXCEPTION; JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; int64_t idx = 0; for (int64_t i = 0; i < len; i++) { JSValue val = JS_GetPropertyInt64(ctx, arg, i); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetPropertyInt64(ctx, result, idx++, val); } for (int64_t i = 0; i < len2; i++) { JSValue val = JS_GetPropertyInt64(ctx, argv[1], i); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetPropertyInt64(ctx, result, idx++, val); } return result; } if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64 || JS_VALUE_GET_TAG(argv[1]) == JS_TAG_INT || JS_VALUE_GET_TAG(argv[1]) == JS_TAG_FLOAT64) { /* Slice */ int from, to; if (JS_ToInt32(ctx, &from, argv[1])) return JS_NULL; if (argc > 2 && !JS_IsNull(argv[2])) { if (JS_ToInt32(ctx, &to, argv[2])) return JS_NULL; } else { to = len; } if (from < 0) from += len; if (to < 0) to += len; if (from < 0 || from > to || to > len) return JS_NULL; JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; int64_t idx = 0; for (int i = from; i < to; i++) { JSValue val = JS_GetPropertyInt64(ctx, arg, i); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetPropertyInt64(ctx, result, idx++, val); } return result; } return JS_NULL; } /* array(object) - keys */ if (JS_IsObject(arg) && !JS_IsArray(ctx, arg)) { /* Return object keys */ return JS_GetOwnPropertyNames2(ctx, arg, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK, JS_ITERATOR_KIND_KEY); } /* array(text) - split into characters */ /* array(text, separator) - split by separator */ /* array(text, length) - dice into chunks */ if (JS_VALUE_IS_TEXT(arg)) { JSString *p = JS_VALUE_GET_STRING(arg); int len = p->len; if (argc < 2 || JS_IsNull(argv[1])) { /* Split into characters */ JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) { return result; } for (int i = 0; i < len; i++) { JSValue ch = js_sub_string(ctx, p, i, i + 1); if (JS_IsException(ch)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetPropertyInt64(ctx, result, i, ch); } return result; } int tag2 = JS_VALUE_GET_TAG(argv[1]); if (JS_VALUE_IS_TEXT(argv[1])) { /* Split by separator */ const char *cstr = JS_ToCString(ctx, arg); const char *sep = JS_ToCString(ctx, argv[1]); if (!cstr || !sep) { if (cstr) JS_FreeCString(ctx, cstr); if (sep) JS_FreeCString(ctx, sep); return JS_EXCEPTION; } JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) { JS_FreeCString(ctx, cstr); JS_FreeCString(ctx, sep); return result; } int64_t idx = 0; size_t sep_len = strlen(sep); const char *pos = cstr; const char *found; if (sep_len == 0) { for (int i = 0; i < len; i++) { JSValue ch = js_sub_string(ctx, p, i, i + 1); JS_SetPropertyInt64(ctx, result, idx++, ch); } } else { while ((found = strstr(pos, sep)) != NULL) { JSValue part = JS_NewStringLen(ctx, pos, found - pos); JS_SetPropertyInt64(ctx, result, idx++, part); pos = found + sep_len; } JSValue part = JS_NewString(ctx, pos); JS_SetPropertyInt64(ctx, result, idx++, part); } JS_FreeCString(ctx, cstr); JS_FreeCString(ctx, sep); return result; } if (JS_IsObject(argv[1]) && JS_IsRegExp(ctx, argv[1])) { /* Split by regex (manual "global" iteration; ignore g flag semantics) */ JSValue rx = argv[1]; JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) { return result; } /* Save & restore lastIndex to avoid mutating caller-visible state */ JSValue orig_last_index = JS_GetPropertyStr(ctx, rx, "lastIndex"); if (JS_IsException(orig_last_index)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } int pos = 0; int64_t out_idx = 0; while (pos <= len) { /* force lastIndex = 0 so flags don't matter and we fully control iteration */ if (JS_SetPropertyStr(ctx, rx, "lastIndex", JS_NewInt32(ctx, 0)) < 0) goto fail_rx_split; JSValue sub_str = js_sub_string(ctx, p, pos, len); if (JS_IsException(sub_str)) goto fail_rx_split; JSValue exec_res = JS_Invoke(ctx, rx, JS_ATOM_exec, 1, (JSValueConst *)&sub_str); JS_FreeValue(ctx, sub_str); if (JS_IsException(exec_res)) goto fail_rx_split; if (JS_IsNull(exec_res)) { JS_FreeValue(ctx, exec_res); /* remainder */ JSValue tail = js_sub_string(ctx, p, pos, len); if (JS_IsException(tail)) goto fail_rx_split; JS_SetPropertyInt64(ctx, result, out_idx++, tail); break; } /* local match index within sub_str */ JSValue idx_val = JS_GetPropertyStr(ctx, exec_res, "index"); if (JS_IsException(idx_val)) { JS_FreeValue(ctx, exec_res); goto fail_rx_split; } int32_t local_index = 0; if (JS_ToInt32(ctx, &local_index, idx_val)) { JS_FreeValue(ctx, idx_val); JS_FreeValue(ctx, exec_res); goto fail_rx_split; } JS_FreeValue(ctx, idx_val); if (local_index < 0) local_index = 0; int found = pos + local_index; if (found < pos) found = pos; if (found > len) { /* treat as no more matches */ JS_FreeValue(ctx, exec_res); JSValue tail = js_sub_string(ctx, p, pos, len); if (JS_IsException(tail)) goto fail_rx_split; JS_SetPropertyInt64(ctx, result, out_idx++, tail); break; } /* match text is exec_res[0] */ JSValue match = JS_GetPropertyUint32(ctx, exec_res, 0); JS_FreeValue(ctx, exec_res); if (JS_IsException(match)) goto fail_rx_split; /* compute match length in code units */ int match_len = 0; { JSValue mstr = JS_ToString(ctx, match); if (JS_IsException(mstr)) { JS_FreeValue(ctx, match); goto fail_rx_split; } JSString *mp = JS_VALUE_GET_STRING(mstr); match_len = (int)mp->len; JS_FreeValue(ctx, mstr); } JS_FreeValue(ctx, match); /* emit piece before match */ JSValue part = js_sub_string(ctx, p, pos, found); if (JS_IsException(part)) goto fail_rx_split; JS_SetPropertyInt64(ctx, result, out_idx++, part); /* advance past match; ensure progress on empty matches */ pos = found + match_len; if (match_len == 0) { if (found >= len) { /* match at end: add trailing empty field and stop */ JSValue empty = JS_NewStringLen(ctx, "", 0); if (JS_IsException(empty)) goto fail_rx_split; JS_SetPropertyInt64(ctx, result, out_idx++, empty); break; } pos = found + 1; } } /* restore lastIndex */ JS_SetPropertyStr(ctx, rx, "lastIndex", orig_last_index); /* str removed - arg not owned */ return result; fail_rx_split: /* best-effort restore lastIndex */ if (!JS_IsException(orig_last_index)) { JS_SetPropertyStr(ctx, rx, "lastIndex", orig_last_index); } else { JS_FreeValue(ctx, orig_last_index); } JS_FreeValue(ctx, result); /* str removed - arg not owned */ return JS_EXCEPTION; } if (JS_VALUE_IS_NUMBER(argv[1])) { /* Dice into chunks */ int chunk_len; if (JS_ToInt32(ctx, &chunk_len, argv[1])) { /* str removed - arg not owned */ return JS_NULL; } if (chunk_len <= 0) { /* str removed - arg not owned */ return JS_NULL; } JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) { /* str removed - arg not owned */ return result; } int64_t idx = 0; for (int i = 0; i < len; i += chunk_len) { int end = i + chunk_len; if (end > len) end = len; JSValue chunk = js_sub_string(ctx, p, i, end); if (JS_IsException(chunk)) { /* str removed - arg not owned */ JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetPropertyInt64(ctx, result, idx++, chunk); } /* str removed - arg not owned */ return result; } /* str removed - arg not owned */ return JS_NULL; } return JS_NULL; } /* array.reduce(arr, fn, initial, reverse) */ static JSValue js_cell_array_reduce(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray(ctx, argv[0])) return JS_NULL; if (!JS_IsFunction(ctx, argv[1])) return JS_NULL; JSValueConst arr = argv[0]; JSValueConst func = argv[1]; int64_t len; if (js_get_length64(ctx, &len, arr)) return JS_EXCEPTION; int reverse = argc > 3 && JS_ToBool(ctx, argv[3]); JSValue acc; if (argc < 3 || JS_IsNull(argv[2])) { if (len == 0) return JS_NULL; if (len == 1) return JS_GetPropertyInt64(ctx, arr, 0); if (reverse) { acc = JS_GetPropertyInt64(ctx, arr, len - 1); for (int64_t i = len - 2; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) { JS_FreeValue(ctx, acc); return JS_EXCEPTION; } JSValue args[2] = { acc, item }; JSValue new_acc = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, acc); JS_FreeValue(ctx, item); if (JS_IsException(new_acc)) return JS_EXCEPTION; acc = new_acc; } } else { acc = JS_GetPropertyInt64(ctx, arr, 0); for (int64_t i = 1; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) { JS_FreeValue(ctx, acc); return JS_EXCEPTION; } JSValue args[2] = { acc, item }; JSValue new_acc = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, acc); JS_FreeValue(ctx, item); if (JS_IsException(new_acc)) return JS_EXCEPTION; acc = new_acc; } } } else { if (len == 0) return JS_DupValue(ctx, argv[2]); acc = JS_DupValue(ctx, argv[2]); if (reverse) { for (int64_t i = len - 1; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) { JS_FreeValue(ctx, acc); return JS_EXCEPTION; } JSValue args[2] = { acc, item }; JSValue new_acc = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, acc); JS_FreeValue(ctx, item); if (JS_IsException(new_acc)) return JS_EXCEPTION; acc = new_acc; } } else { for (int64_t i = 0; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) { JS_FreeValue(ctx, acc); return JS_EXCEPTION; } JSValue args[2] = { acc, item }; JSValue new_acc = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, acc); JS_FreeValue(ctx, item); if (JS_IsException(new_acc)) return JS_EXCEPTION; acc = new_acc; } } } return acc; } /* array.for(arr, fn, reverse, exit) */ static JSValue js_cell_array_for(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray(ctx, argv[0])) return JS_NULL; if (!JS_IsFunction(ctx, argv[1])) return JS_NULL; JSValueConst arr = argv[0]; JSValueConst func = argv[1]; int64_t len; if (js_get_length64(ctx, &len, arr)) return JS_EXCEPTION; if (len == 0) return JS_NULL; int reverse = argc > 2 && JS_ToBool(ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; if (reverse) { for (int64_t i = len - 1; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; JSValue args[2] = { item, JS_NewInt64(ctx, i) }; JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, item); JS_FreeValue(ctx, args[1]); if (JS_IsException(result)) return JS_EXCEPTION; if (!JS_IsNull(exit_val) && js_strict_eq(ctx, result, exit_val)) { return result; } JS_FreeValue(ctx, result); } } else { for (int64_t i = 0; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; JSValue args[2] = { item, JS_NewInt64(ctx, i) }; JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, item); JS_FreeValue(ctx, args[1]); if (JS_IsException(result)) return JS_EXCEPTION; if (!JS_IsNull(exit_val) && js_strict_eq(ctx, result, exit_val)) { return result; } JS_FreeValue(ctx, result); } } return JS_NULL; } /* array.find(arr, fn, reverse, from) */ static JSValue js_cell_array_find(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray(ctx, argv[0])) return JS_NULL; JSValueConst arr = argv[0]; int64_t len; if (js_get_length64(ctx, &len, arr)) return JS_EXCEPTION; int reverse = argc > 2 && JS_ToBool(ctx, argv[2]); int64_t from; if (argc > 3 && !JS_IsNull(argv[3])) { if (JS_ToInt64(ctx, &from, argv[3])) return JS_NULL; } else { from = reverse ? len - 1 : 0; } if (!JS_IsFunction(ctx, argv[1])) { /* Compare exactly */ JSValue target = argv[1]; if (reverse) { for (int64_t i = from; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; if (js_strict_eq(ctx, item, target)) { JS_FreeValue(ctx, item); return JS_NewInt64(ctx, i); } JS_FreeValue(ctx, item); } } else { for (int64_t i = from; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; if (js_strict_eq(ctx, item, target)) { JS_FreeValue(ctx, item); return JS_NewInt64(ctx, i); } JS_FreeValue(ctx, item); } } return JS_NULL; } /* Use function predicate */ JSValueConst func = argv[1]; if (reverse) { for (int64_t i = from; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; JSValue args[2] = { item, JS_NewInt64(ctx, i) }; JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, item); JS_FreeValue(ctx, args[1]); if (JS_IsException(result)) return JS_EXCEPTION; if (JS_ToBool(ctx, result)) { JS_FreeValue(ctx, result); return JS_NewInt64(ctx, i); } JS_FreeValue(ctx, result); } } else { for (int64_t i = from; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; JSValue args[2] = { item, JS_NewInt64(ctx, i) }; JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, item); JS_FreeValue(ctx, args[1]); if (JS_IsException(result)) return JS_EXCEPTION; if (JS_ToBool(ctx, result)) { JS_FreeValue(ctx, result); return JS_NewInt64(ctx, i); } JS_FreeValue(ctx, result); } } return JS_NULL; } /* array.filter(arr, fn) */ static JSValue js_cell_array_filter(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray(ctx, argv[0])) return JS_NULL; if (!JS_IsFunction(ctx, argv[1])) return JS_NULL; JSValueConst arr = argv[0]; JSValueConst func = argv[1]; int64_t len; if (js_get_length64(ctx, &len, arr)) return JS_EXCEPTION; JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; int64_t out_idx = 0; for (int64_t i = 0; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } JSValue args[2] = { item, JS_NewInt64(ctx, i) }; JSValue val = JS_Call(ctx, func, JS_NULL, 2, args); JS_FreeValue(ctx, args[1]); if (JS_IsException(val)) { JS_FreeValue(ctx, item); JS_FreeValue(ctx, result); return JS_EXCEPTION; } if (JS_VALUE_GET_TAG(val) == JS_TAG_BOOL) { if (JS_VALUE_GET_BOOL(val)) { JS_SetPropertyInt64(ctx, result, out_idx++, item); } else { JS_FreeValue(ctx, item); } } else { JS_FreeValue(ctx, item); JS_FreeValue(ctx, val); JS_FreeValue(ctx, result); return JS_NULL; } JS_FreeValue(ctx, val); } return result; } /* array.sort(arr, select) */ static JSValue js_cell_array_sort(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; if (!JS_IsArray(ctx, argv[0])) return JS_NULL; JSValueConst arr = argv[0]; int64_t len; if (js_get_length64(ctx, &len, arr)) return JS_EXCEPTION; /* Copy array */ JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; if (len == 0) return result; JSValue *items = js_malloc(ctx, sizeof(JSValue) * len); double *keys = js_malloc(ctx, sizeof(double) * len); char **str_keys = NULL; int is_string = 0; if (!items || !keys) { if (items) js_free(ctx, items); if (keys) js_free(ctx, keys); JS_FreeValue(ctx, result); return JS_EXCEPTION; } /* Extract keys */ for (int64_t i = 0; i < len; i++) { items[i] = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(items[i])) { for (int64_t j = 0; j < i; j++) JS_FreeValue(ctx, items[j]); js_free(ctx, items); js_free(ctx, keys); JS_FreeValue(ctx, result); return JS_EXCEPTION; } JSValue key; if (argc < 2 || JS_IsNull(argv[1])) { key = JS_DupValue(ctx, items[i]); } else if (JS_VALUE_GET_TAG(argv[1]) == JS_TAG_STRING || JS_VALUE_GET_TAG(argv[1]) == JS_TAG_INT) { key = JS_GetProperty(ctx, items[i], JS_ValueToAtom(ctx, argv[1])); } else if (JS_IsArray(ctx, argv[1])) { key = JS_GetPropertyInt64(ctx, argv[1], i); } else { key = JS_DupValue(ctx, items[i]); } if (JS_IsException(key)) { for (int64_t j = 0; j <= i; j++) JS_FreeValue(ctx, items[j]); js_free(ctx, items); js_free(ctx, keys); JS_FreeValue(ctx, result); return JS_EXCEPTION; } int key_tag = JS_VALUE_GET_TAG(key); if (key_tag == JS_TAG_INT || key_tag == JS_TAG_FLOAT64) { JS_ToFloat64(ctx, &keys[i], key); if (i == 0) is_string = 0; } else if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_ROPE) { if (i == 0) { is_string = 1; str_keys = js_malloc(ctx, sizeof(char*) * len); if (!str_keys) { JS_FreeValue(ctx, key); for (int64_t j = 0; j <= i; j++) JS_FreeValue(ctx, items[j]); js_free(ctx, items); js_free(ctx, keys); JS_FreeValue(ctx, result); return JS_EXCEPTION; } } if (is_string) { str_keys[i] = (char*)JS_ToCString(ctx, key); } } else { JS_FreeValue(ctx, key); for (int64_t j = 0; j <= i; j++) JS_FreeValue(ctx, items[j]); js_free(ctx, items); js_free(ctx, keys); if (str_keys) { for (int64_t j = 0; j < i; j++) JS_FreeCString(ctx, str_keys[j]); js_free(ctx, str_keys); } JS_FreeValue(ctx, result); return JS_NULL; } JS_FreeValue(ctx, key); } /* Create index array */ int64_t *indices = js_malloc(ctx, sizeof(int64_t) * len); if (!indices) { for (int64_t j = 0; j < len; j++) JS_FreeValue(ctx, items[j]); js_free(ctx, items); js_free(ctx, keys); if (str_keys) { for (int64_t j = 0; j < len; j++) JS_FreeCString(ctx, str_keys[j]); js_free(ctx, str_keys); } JS_FreeValue(ctx, result); return JS_EXCEPTION; } for (int64_t i = 0; i < len; i++) indices[i] = i; /* Simple insertion sort (stable) */ for (int64_t i = 1; i < len; i++) { int64_t temp = indices[i]; int64_t j = i - 1; while (j >= 0) { int cmp; if (is_string) { cmp = strcmp(str_keys[indices[j]], str_keys[temp]); } else { double a = keys[indices[j]], b = keys[temp]; cmp = (a > b) - (a < b); } if (cmp <= 0) break; indices[j + 1] = indices[j]; j--; } indices[j + 1] = temp; } /* Build sorted array */ for (int64_t i = 0; i < len; i++) { JS_SetPropertyInt64(ctx, result, i, JS_DupValue(ctx, items[indices[i]])); } /* Cleanup */ for (int64_t i = 0; i < len; i++) JS_FreeValue(ctx, items[i]); js_free(ctx, items); js_free(ctx, keys); js_free(ctx, indices); if (str_keys) { for (int64_t i = 0; i < len; i++) JS_FreeCString(ctx, str_keys[i]); js_free(ctx, str_keys); } return result; } /* ---------------------------------------------------------------------------- * object function and sub-functions * ---------------------------------------------------------------------------- */ /* object(arg, arg2) - main object function */ static JSValue js_cell_object(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue arg = argv[0]; /* object(object) - shallow mutable copy */ if (JS_IsObject(arg) && !JS_IsArray(ctx, arg) && !JS_IsFunction(ctx, arg)) { if (argc < 2 || JS_IsNull(argv[1])) { /* Shallow copy */ JSValue result = JS_NewObject(ctx); if (JS_IsException(result)) return result; JSPropertyEnum *props; uint32_t len; if (JS_GetOwnPropertyNames(ctx, &props, &len, arg, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue val = JS_GetProperty(ctx, arg, props[i].atom); if (JS_IsException(val)) { JS_FreePropertyEnum(ctx, props, len); JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetProperty(ctx, result, props[i].atom, val); } JS_FreePropertyEnum(ctx, props, len); return result; } /* object(object, another_object) - combine */ if (JS_IsObject(argv[1]) && !JS_IsArray(ctx, argv[1])) { JSValue result = JS_NewObject(ctx); if (JS_IsException(result)) return result; /* Copy from first object */ JSPropertyEnum *props; uint32_t len; if (JS_GetOwnPropertyNames(ctx, &props, &len, arg, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue val = JS_GetProperty(ctx, arg, props[i].atom); if (JS_IsException(val)) { JS_FreePropertyEnum(ctx, props, len); JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetProperty(ctx, result, props[i].atom, val); } JS_FreePropertyEnum(ctx, props, len); /* Copy from second object */ if (JS_GetOwnPropertyNames(ctx, &props, &len, argv[1], JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue val = JS_GetProperty(ctx, argv[1], props[i].atom); if (JS_IsException(val)) { JS_FreePropertyEnum(ctx, props, len); JS_FreeValue(ctx, result); return JS_EXCEPTION; } JS_SetProperty(ctx, result, props[i].atom, val); } JS_FreePropertyEnum(ctx, props, len); return result; } /* object(object, array_of_keys) - select */ if (JS_IsArray(ctx, argv[1])) { JSValue result = JS_NewObject(ctx); if (JS_IsException(result)) return result; int64_t len; if (js_get_length64(ctx, &len, argv[1])) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } for (int64_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyInt64(ctx, argv[1], i); if (JS_IsException(key)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } int key_tag = JS_VALUE_GET_TAG(key); if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_ROPE) { JSAtom atom = JS_ValueToAtom(ctx, key); int has = JS_HasProperty(ctx, arg, atom); if (has > 0) { JSValue val = JS_GetProperty(ctx, arg, atom); if (!JS_IsException(val)) { JS_SetProperty(ctx, result, atom, val); } } JS_FreeAtom(ctx, atom); } JS_FreeValue(ctx, key); } return result; } } /* object(array_of_keys) - set with true values */ /* object(array_of_keys, value) - value set */ /* object(array_of_keys, function) - functional value set */ if (JS_IsArray(ctx, arg)) { int64_t len; if (js_get_length64(ctx, &len, arg)) return JS_EXCEPTION; JSValue result = JS_NewObject(ctx); if (JS_IsException(result)) return result; for (int64_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyInt64(ctx, arg, i); if (JS_IsException(key)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } int key_tag = JS_VALUE_GET_TAG(key); if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_ROPE) { JSAtom atom = JS_ValueToAtom(ctx, key); JSValue val; if (argc < 2 || JS_IsNull(argv[1])) { val = JS_TRUE; } else if (JS_IsFunction(ctx, argv[1])) { val = JS_Call(ctx, argv[1], JS_NULL, 1, &key); if (JS_IsException(val)) { JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, key); JS_FreeValue(ctx, result); return JS_EXCEPTION; } } else { val = JS_DupValue(ctx, argv[1]); } JS_SetProperty(ctx, result, atom, val); JS_FreeAtom(ctx, atom); } JS_FreeValue(ctx, key); } return result; } return JS_NULL; } /* object.values(obj) */ static JSValue js_cell_object_values(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; return JS_GetOwnPropertyNames2(ctx, argv[0], JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK, JS_ITERATOR_KIND_VALUE); } /* object.assign(obj, ...args) */ static JSValue js_cell_object_assign(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; return js_object_assign(ctx, this_val, argc, argv); } /* object.has(obj, key) - check if object has own property */ static JSValue js_cell_object_has(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_NULL; if (!JS_IsObject(argv[0])) return JS_NULL; JSAtom atom = JS_ValueToAtom(ctx, argv[1]); if (atom == JS_ATOM_NULL) return JS_EXCEPTION; int ret = JS_HasProperty(ctx, argv[0], atom); JS_FreeAtom(ctx, atom); if (ret < 0) return JS_EXCEPTION; return JS_NewBool(ctx, ret); } static const JSCFunctionListEntry js_cell_object_funcs[] = { JS_CFUNC_DEF("values", 1, js_cell_object_values), JS_CFUNC_DEF("assign", 2, js_cell_object_assign), JS_CFUNC_DEF("has", 2, js_cell_object_has), }; /* ---------------------------------------------------------------------------- * fn function and sub-functions * ---------------------------------------------------------------------------- */ /* fn.apply(func, args) */ static JSValue js_cell_fn_apply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; if (!JS_IsFunction(ctx, argv[0])) return JS_DupValue(ctx, argv[0]); JSValueConst func = argv[0]; if (argc < 2) { return JS_Call(ctx, func, JS_NULL, 0, NULL); } JSValue args_val = argv[1]; if (!JS_IsArray(ctx, args_val)) { /* Wrap single value in array */ return JS_Call(ctx, func, JS_NULL, 1, &args_val); } int64_t len; if (js_get_length64(ctx, &len, args_val)) return JS_EXCEPTION; /* Check arity */ JSValue func_len = JS_GetPropertyStr(ctx, func, "length"); if (!JS_IsException(func_len)) { int arity; if (!JS_ToInt32(ctx, &arity, func_len)) { if (len > arity) { JS_FreeValue(ctx, func_len); return JS_ThrowTypeError(ctx, "fn.apply: too many arguments"); } } JS_FreeValue(ctx, func_len); } JSValue *args = js_malloc(ctx, sizeof(JSValue) * (len > 0 ? len : 1)); if (!args) return JS_EXCEPTION; for (int64_t i = 0; i < len; i++) { args[i] = JS_GetPropertyInt64(ctx, args_val, i); if (JS_IsException(args[i])) { for (int64_t j = 0; j < i; j++) JS_FreeValue(ctx, args[j]); js_free(ctx, args); return JS_EXCEPTION; } } JSValue result = JS_Call(ctx, func, JS_NULL, len, args); for (int64_t i = 0; i < len; i++) JS_FreeValue(ctx, args[i]); js_free(ctx, args); return result; } /* ============================================================================ * Blob Intrinsic Type * ============================================================================ */ /* Helper to check if JSValue is a blob */ static blob *js_get_blob(JSContext *ctx, JSValueConst val) { if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return NULL; JSObject *p = JS_VALUE_GET_OBJ(val); if (p->class_id != JS_CLASS_BLOB) return NULL; return p->u.opaque; } /* Helper to create a new blob JSValue */ static JSValue js_new_blob(JSContext *ctx, blob *b) { JSValue obj = JS_NewObjectClass(ctx, JS_CLASS_BLOB); if (JS_IsException(obj)) { blob_destroy(b); return obj; } JS_SetOpaque(obj, b); return obj; } /* Blob finalizer */ static void js_blob_finalizer(JSRuntime *rt, JSValue val) { blob *b = JS_GetOpaque(val, JS_CLASS_BLOB); if (b) blob_destroy(b); } /* blob() constructor */ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { blob *bd = NULL; /* blob() - empty blob */ if (argc == 0) { bd = blob_new(0); } /* blob(capacity) - blob with initial capacity in bits */ else if (argc == 1 && JS_IsNumber(argv[0])) { int64_t capacity_bits; if (JS_ToInt64(ctx, &capacity_bits, argv[0]) < 0) return JS_EXCEPTION; if (capacity_bits < 0) capacity_bits = 0; bd = blob_new((size_t)capacity_bits); } /* blob(length, logical/random) - blob with fill or random */ else if (argc == 2 && JS_IsNumber(argv[0])) { int64_t length_bits; if (JS_ToInt64(ctx, &length_bits, argv[0]) < 0) return JS_EXCEPTION; if (length_bits < 0) length_bits = 0; if (JS_IsBool(argv[1])) { int is_one = JS_ToBool(ctx, argv[1]); bd = blob_new_with_fill((size_t)length_bits, is_one); } else if (JS_IsFunction(ctx, argv[1])) { /* Random function provided */ size_t bytes = (length_bits + 7) / 8; bd = blob_new((size_t)length_bits); if (bd) { bd->length = length_bits; memset(bd->data, 0, bytes); size_t bits_written = 0; while (bits_written < (size_t)length_bits) { JSValue randval = JS_Call(ctx, argv[1], JS_NULL, 0, NULL); if (JS_IsException(randval)) { blob_destroy(bd); return JS_EXCEPTION; } int64_t fitval; JS_ToInt64(ctx, &fitval, randval); JS_FreeValue(ctx, randval); size_t bits_to_use = length_bits - bits_written; if (bits_to_use > 52) bits_to_use = 52; for (size_t j = 0; j < bits_to_use; j++) { size_t bit_pos = bits_written + j; size_t byte_idx = bit_pos / 8; size_t bit_idx = bit_pos % 8; if (fitval & (1LL << j)) bd->data[byte_idx] |= (uint8_t)(1 << bit_idx); else bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx); } bits_written += bits_to_use; } } } else { return JS_ThrowTypeError(ctx, "Second argument must be boolean or random function"); } } /* blob(blob, from, to) - copy from another blob */ else if (argc >= 1 && JS_IsObject(argv[0])) { blob *src = js_get_blob(ctx, argv[0]); if (!src) return JS_ThrowTypeError(ctx, "blob constructor: argument 1 not a blob"); int64_t from = 0, to = (int64_t)src->length; if (argc >= 2 && JS_IsNumber(argv[1])) { JS_ToInt64(ctx, &from, argv[1]); if (from < 0) from = 0; } if (argc >= 3 && JS_IsNumber(argv[2])) { JS_ToInt64(ctx, &to, argv[2]); if (to < from) to = from; if (to > (int64_t)src->length) to = (int64_t)src->length; } bd = blob_new_from_blob(src, (size_t)from, (size_t)to); } /* blob(text) - create blob from UTF-8 string */ else if (argc == 1 && (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_STRING || JS_VALUE_GET_TAG(argv[0]) == JS_TAG_STRING_ROPE)) { const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; size_t len = strlen(str); bd = blob_new(len * 8); if (bd) { memcpy(bd->data, str, len); bd->length = len * 8; } JS_FreeCString(ctx, str); } else { return JS_ThrowTypeError(ctx, "blob constructor: invalid arguments"); } if (!bd) return JS_ThrowOutOfMemory(ctx); return js_new_blob(ctx, bd); } /* blob.write_bit(logical) */ static JSValue js_blob_write_bit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "write_bit(logical) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "write_bit: not called on a blob"); int bit_val; if (JS_IsNumber(argv[0])) { int32_t num; JS_ToInt32(ctx, &num, argv[0]); if (num != 0 && num != 1) return JS_ThrowTypeError(ctx, "write_bit: value must be true, false, 0, or 1"); bit_val = num; } else { bit_val = JS_ToBool(ctx, argv[0]); } if (blob_write_bit(bd, bit_val) < 0) return JS_ThrowTypeError(ctx, "write_bit: cannot write (maybe stone or OOM)"); return JS_NULL; } /* blob.write_blob(second_blob) */ static JSValue js_blob_write_blob(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "write_blob(second_blob) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "write_blob: not called on a blob"); blob *second = js_get_blob(ctx, argv[0]); if (!second) return JS_ThrowTypeError(ctx, "write_blob: argument must be a blob"); if (blob_write_blob(bd, second) < 0) return JS_ThrowTypeError(ctx, "write_blob: cannot write to stone blob or OOM"); return JS_NULL; } /* blob.write_number(number) - write dec64 */ static JSValue js_blob_write_number(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "write_number(number) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "write_number: not called on a blob"); double d; if (JS_ToFloat64(ctx, &d, argv[0]) < 0) return JS_EXCEPTION; if (blob_write_dec64(bd, d) < 0) return JS_ThrowTypeError(ctx, "write_number: cannot write to stone blob or OOM"); return JS_NULL; } /* blob.write_fit(value, len) */ static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_ThrowTypeError(ctx, "write_fit(value, len) requires 2 arguments"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "write_fit: not called on a blob"); int64_t value; int32_t len; if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION; if (blob_write_fit(bd, value, len) < 0) return JS_ThrowTypeError(ctx, "write_fit: value doesn't fit or stone blob"); return JS_NULL; } /* blob.write_text(text) */ static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "write_text(text) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "write_text: not called on a blob"); const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; if (blob_write_text(bd, str) < 0) { JS_FreeCString(ctx, str); return JS_ThrowTypeError(ctx, "write_text: cannot write to stone blob or OOM"); } JS_FreeCString(ctx, str); return JS_NULL; } /* blob.write_pad(block_size) */ static JSValue js_blob_write_pad(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "write_pad(block_size) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "write_pad: not called on a blob"); int32_t block_size; if (JS_ToInt32(ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION; if (blob_write_pad(bd, block_size) < 0) return JS_ThrowTypeError(ctx, "write_pad: cannot write"); return JS_NULL; } /* blob.w16(value) - write 16-bit value */ static JSValue js_blob_w16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "w16(value) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "w16: not called on a blob"); int32_t value; if (JS_ToInt32(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; int16_t short_val = (int16_t)value; if (blob_write_bytes(bd, &short_val, sizeof(int16_t)) < 0) return JS_ThrowTypeError(ctx, "w16: cannot write"); return JS_NULL; } /* blob.w32(value) - write 32-bit value */ static JSValue js_blob_w32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "w32(value) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "w32: not called on a blob"); int32_t value; if (JS_ToInt32(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; if (blob_write_bytes(bd, &value, sizeof(int32_t)) < 0) return JS_ThrowTypeError(ctx, "w32: cannot write"); return JS_NULL; } /* blob.wf(value) - write float */ static JSValue js_blob_wf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "wf(value) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "wf: not called on a blob"); float f; int tag = JS_VALUE_GET_TAG(argv[0]); if (tag == JS_TAG_INT) { f = (float)JS_VALUE_GET_INT(argv[0]); } else if (tag == JS_TAG_FLOAT64) { f = (float)JS_VALUE_GET_FLOAT64(argv[0]); } else { double d; if (JS_ToFloat64(ctx, &d, argv[0]) < 0) return JS_EXCEPTION; f = (float)d; } if (blob_write_bytes(bd, &f, sizeof(f)) < 0) return JS_ThrowTypeError(ctx, "wf: cannot write"); return JS_NULL; } /* blob.read_logical(from) */ static JSValue js_blob_read_logical(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "read_logical(from) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "read_logical: not called on a blob"); int64_t pos; if (JS_ToInt64(ctx, &pos, argv[0]) < 0) return JS_ThrowInternalError(ctx, "must provide a positive bit"); if (pos < 0) return JS_ThrowRangeError(ctx, "read_logical: position must be non-negative"); int bit_val; if (blob_read_bit(bd, (size_t)pos, &bit_val) < 0) return JS_ThrowTypeError(ctx, "read_logical: blob must be stone"); return JS_NewBool(ctx, bit_val); } /* blob.read_blob(from, to) */ static JSValue js_blob_read_blob(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "read_blob: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError(ctx, "read_blob: blob must be stone"); int64_t from = 0; int64_t to = bd->length; if (argc >= 1) { if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (from < 0) from = 0; } if (argc >= 2) { if (JS_ToInt64(ctx, &to, argv[1]) < 0) return JS_EXCEPTION; if (to > (int64_t)bd->length) to = bd->length; } blob *new_bd = blob_read_blob(bd, from, to); if (!new_bd) return JS_ThrowOutOfMemory(ctx); return js_new_blob(ctx, new_bd); } /* blob.read_number(from) */ static JSValue js_blob_read_number(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "read_number(from) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "read_number: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError(ctx, "read_number: blob must be stone"); double from; if (JS_ToFloat64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (from < 0) return JS_ThrowRangeError(ctx, "read_number: position must be non-negative"); double d; if (blob_read_dec64(bd, from, &d) < 0) return JS_ThrowRangeError(ctx, "read_number: out of range"); return JS_NewFloat64(ctx, d); } /* blob.read_fit(from, len) */ static JSValue js_blob_read_fit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_ThrowTypeError(ctx, "read_fit(from, len) requires 2 arguments"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "read_fit: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError(ctx, "read_fit: blob must be stone"); int64_t from; int32_t len; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION; if (from < 0) return JS_ThrowRangeError(ctx, "read_fit: position must be non-negative"); int64_t value; if (blob_read_fit(bd, from, len, &value) < 0) return JS_ThrowRangeError(ctx, "read_fit: out of range or invalid length"); return JS_NewInt64(ctx, value); } /* blob.read_text(from) */ static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "read_text: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError(ctx, "read_text: blob must be stone"); int64_t from = 0; if (argc >= 1) { if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; } char *text; size_t bits_read; if (blob_read_text(bd, from, &text, &bits_read) < 0) return JS_ThrowRangeError(ctx, "read_text: out of range or invalid encoding"); JSValue result = JS_NewString(ctx, text); /* Note: blob_read_text uses system malloc, so we use sys_free */ sys_free(text); return result; } /* blob.pad?(from, block_size) */ static JSValue js_blob_pad_q(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_ThrowTypeError(ctx, "pad?(from, block_size) requires 2 arguments"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "pad?: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError(ctx, "pad?: blob must be stone"); int64_t from; int32_t block_size; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &block_size, argv[1]) < 0) return JS_EXCEPTION; return JS_NewBool(ctx, blob_pad_check(bd, from, block_size)); } static const JSCFunctionListEntry js_blob_proto_funcs[] = { /* Write methods */ JS_CFUNC_DEF("write_bit", 1, js_blob_write_bit), JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob), JS_CFUNC_DEF("write_number", 1, js_blob_write_number), JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit), JS_CFUNC_DEF("write_text", 1, js_blob_write_text), JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad), JS_CFUNC_DEF("wf", 1, js_blob_wf), JS_CFUNC_DEF("w16", 1, js_blob_w16), JS_CFUNC_DEF("w32", 1, js_blob_w32), /* Read methods */ JS_CFUNC_DEF("read_logical", 1, js_blob_read_logical), JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob), JS_CFUNC_DEF("read_number", 1, js_blob_read_number), JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit), JS_CFUNC_DEF("read_text", 1, js_blob_read_text), JS_CFUNC_DEF("pad?", 2, js_blob_pad_q), }; /* ============================================================================ * Blob external API functions (called from other files via cell.h) * ============================================================================ */ /* Initialize blob - called during context setup (but we do it in JS_AddIntrinsicBaseObjects now) */ JSValue js_blob_use(JSContext *js) { /* Blob is now initialized as an intrinsic in JS_AddIntrinsicBaseObjects */ /* Return the blob constructor for compatibility */ return JS_GetPropertyStr(js, js->global_obj, "blob"); } /* Create a new blob from raw data, stone it, and return as JSValue */ JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes) { blob *b = blob_new(bytes * 8); if (!b) return JS_ThrowOutOfMemory(js); memcpy(b->data, data, bytes); b->length = bytes * 8; blob_make_stone(b); return js_new_blob(js, b); } /* Get raw data pointer from a blob (must be stone) - returns byte count */ void *js_get_blob_data(JSContext *js, size_t *size, JSValue v) { blob *b = js_get_blob(js, v); *size = (b->length + 7) / 8; if (!b) { JS_ThrowReferenceError(js, "get_blob_data: not called on a blob"); return NULL; } if (!b->is_stone) { JS_ThrowReferenceError(js, "attempted to read data from a non-stone blob"); return NULL; } if (b->length % 8 != 0) { JS_ThrowReferenceError(js, "attempted to read data from a non-byte aligned blob [length is %zu]", b->length); return NULL; } return b->data; } /* Get raw data pointer from a blob (must be stone) - returns bit count */ void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v) { blob *b = js_get_blob(js, v); if (!b) { JS_ThrowReferenceError(js, "get_blob_data_bits: not called on a blob"); return NULL; } if (!b->is_stone) { JS_ThrowReferenceError(js, "attempted to read data from a non-stone blob"); return NULL; } if (!b->data) { JS_ThrowReferenceError(js, "attempted to read data from an empty blob"); return NULL; } if (b->length % 8 != 0) { JS_ThrowReferenceError(js, "attempted to read data from a non-byte aligned blob"); return NULL; } if (b->length == 0) { JS_ThrowReferenceError(js, "attempted to read data from an empty blob"); return NULL; } *bits = b->length; return b->data; } /* Check if a value is a blob */ int js_is_blob(JSContext *js, JSValue v) { return js_get_blob(js, v) != NULL; } /* ============================================================================ * stone() function - deep freeze with blob support * ============================================================================ */ static JSValue js_cell_stone_deep(JSContext *ctx, JSValueConst obj) { /* Handle blob specially */ blob *bd = js_get_blob(ctx, obj); if (bd) { if (!bd->is_stone) blob_make_stone(bd); return JS_DupValue(ctx, obj); } /* Handle non-objects (primitives are already immutable) */ if (!JS_IsObject(obj)) return JS_DupValue(ctx, obj); /* Get all property keys */ JSPropertyEnum *tab; uint32_t len; if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY) < 0) { return JS_EXCEPTION; } /* Recursively freeze all properties */ for (uint32_t i = 0; i < len; i++) { JSValue val = JS_GetProperty(ctx, obj, tab[i].atom); if (JS_IsException(val)) { for (uint32_t j = i; j < len; j++) JS_FreeAtom(ctx, tab[j].atom); js_free(ctx, tab); return JS_EXCEPTION; } if (JS_IsObject(val) || JS_IsFunction(ctx, val)) { JSValue frozen = js_cell_stone_deep(ctx, val); JS_FreeValue(ctx, val); if (JS_IsException(frozen)) { for (uint32_t j = i; j < len; j++) JS_FreeAtom(ctx, tab[j].atom); js_free(ctx, tab); return JS_EXCEPTION; } JS_FreeValue(ctx, frozen); } else { JS_FreeValue(ctx, val); } JS_FreeAtom(ctx, tab[i].atom); } js_free(ctx, tab); /* Freeze the object itself */ JSValue args[1] = { JS_DupValue(ctx, obj) }; JSValue result = js_object_seal(ctx, JS_NULL, 1, args, 1); /* freeze */ JS_FreeValue(ctx, args[0]); if (JS_IsException(result)) return result; JS_FreeValue(ctx, result); return JS_DupValue(ctx, obj); } /* stone(object) - deep freeze an object */ static JSValue js_cell_stone(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; return js_cell_stone_deep(ctx, argv[0]); } /* stone.p(object) - check if object is frozen/stone */ static JSValue js_cell_stone_p(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; JSValue obj = argv[0]; /* Check blob */ blob *bd = js_get_blob(ctx, obj); if (bd) return JS_NewBool(ctx, bd->is_stone); /* Non-objects are immutable by nature */ if (!JS_IsObject(obj)) return JS_TRUE; /* Check if object is frozen */ return JS_NewBool(ctx, JS_IsExtensible(ctx, obj) == 0); } static const JSCFunctionListEntry js_cell_stone_funcs[] = { JS_CFUNC_DEF("p", 1, js_cell_stone_p), }; /* ============================================================================ * reverse() function - reverse an array * ============================================================================ */ static JSValue js_cell_reverse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue value = argv[0]; /* Handle arrays */ if (JS_IsArray(ctx, value)) { JSValue length_val = JS_GetPropertyStr(ctx, value, "length"); int64_t len; if (JS_ToInt64(ctx, &len, length_val) < 0) { JS_FreeValue(ctx, length_val); return JS_NULL; } JS_FreeValue(ctx, length_val); JSValue result = JS_NewArray(ctx); for (int64_t i = len - 1, j = 0; i >= 0; i--, j++) { JSValue elem = JS_GetPropertyInt64(ctx, value, i); JS_SetPropertyInt64(ctx, result, j, elem); } return result; } /* Handle blobs */ blob *bd = js_get_blob(ctx, value); if (bd) { /* Blobs need proper blob reversal support - return null for now */ return JS_NULL; } return JS_NULL; } /* ============================================================================ * proto() function - get prototype of an object * ============================================================================ */ static JSValue js_cell_proto(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue obj = argv[0]; if (!JS_IsObject(obj)) return JS_NULL; JSValue proto = JS_GetPrototype(ctx, obj); if (JS_IsException(proto)) return JS_NULL; /* If prototype is Object.prototype, return null */ if (JS_IsObject(proto)) { JSValue obj_proto = JS_GetPropertyStr(ctx, ctx->class_proto[JS_CLASS_OBJECT], ""); if (JS_VALUE_GET_OBJ(proto) == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) { JS_FreeValue(ctx, proto); JS_FreeValue(ctx, obj_proto); return JS_NULL; } JS_FreeValue(ctx, obj_proto); } return proto; } static JSValue js_cell_meme(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue proto = JS_NULL; if (argc > 0 && !JS_IsNull(argv[0])) proto = argv[0]; JSValue result = JS_NewObjectProto(ctx, proto); if (JS_IsException(result)) return result; if (argc < 2) return result; JSValue mixins = argv[1]; /* Helper function to apply a single mixin */ #define APPLY_MIXIN(mix) do { \ if (!JS_IsObject(mix) || JS_IsNull(mix) || JS_IsArray(ctx, mix)) \ break; \ JSPropertyEnum *tab; \ uint32_t len; \ if (JS_GetOwnPropertyNames(ctx, &tab, &len, mix, \ JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) { \ JS_FreeValue(ctx, result); \ return JS_EXCEPTION; \ } \ for (uint32_t j = 0; j < len; j++) { \ JSValue val = JS_GetProperty(ctx, mix, tab[j].atom); \ if (JS_IsException(val)) { \ for (uint32_t k = j; k < len; k++) \ JS_FreeAtom(ctx, tab[k].atom); \ js_free(ctx, tab); \ JS_FreeValue(ctx, result); \ return JS_EXCEPTION; \ } \ JS_SetProperty(ctx, result, tab[j].atom, val); \ JS_FreeAtom(ctx, tab[j].atom); \ } \ js_free(ctx, tab); \ } while (0) if (JS_IsArray(ctx, mixins)) { /* Array of mixins */ int64_t len; if (js_get_length64(ctx, &len, mixins)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } for (int64_t i = 0; i < len; i++) { JSValue mix = JS_GetPropertyInt64(ctx, mixins, i); if (JS_IsException(mix)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; } APPLY_MIXIN(mix); JS_FreeValue(ctx, mix); } } else if (JS_IsObject(mixins) && !JS_IsNull(mixins)) { /* Single mixin object */ APPLY_MIXIN(mixins); } #undef APPLY_MIXIN return result; } /* ============================================================================ * splat() function - flatten object with prototype chain * ============================================================================ */ static JSValue js_cell_splat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue obj = argv[0]; if (!JS_IsObject(obj) || JS_IsNull(obj)) return JS_NULL; JSValue result = JS_NewObject(ctx); if (JS_IsException(result)) return JS_EXCEPTION; JSValue current = JS_DupValue(ctx, obj); /* Walk prototype chain and collect text keys */ while (!JS_IsNull(current)) { JSPropertyEnum *tab; uint32_t len; if (JS_GetOwnPropertyNames(ctx, &tab, &len, current, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) { JS_FreeValue(ctx, current); JS_FreeValue(ctx, result); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSAtom atom = tab[i].atom; /* Check if property not already in result */ int has = JS_HasProperty(ctx, result, atom); if (has < 0) { for (uint32_t j = i; j < len; j++) JS_FreeAtom(ctx, tab[j].atom); js_free(ctx, tab); JS_FreeValue(ctx, current); JS_FreeValue(ctx, result); return JS_EXCEPTION; } if (!has) { JSValue val = JS_GetProperty(ctx, current, atom); if (JS_IsException(val)) { for (uint32_t j = i; j < len; j++) JS_FreeAtom(ctx, tab[j].atom); js_free(ctx, tab); JS_FreeValue(ctx, current); JS_FreeValue(ctx, result); return JS_EXCEPTION; } /* Only include serializable types */ int tag = JS_VALUE_GET_TAG(val); if (JS_IsObject(val) || JS_IsNumber(val) || tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE || tag == JS_TAG_BOOL) { JS_SetProperty(ctx, result, atom, val); } else { JS_FreeValue(ctx, val); } } JS_FreeAtom(ctx, tab[i].atom); } js_free(ctx, tab); JSValue next = JS_GetPrototype(ctx, current); JS_FreeValue(ctx, current); current = next; } /* Call to_data if present */ JSValue to_data = JS_GetPropertyStr(ctx, obj, "to_data"); if (JS_IsFunction(ctx, to_data)) { JSValue args[1] = { result }; JSValue extra = JS_Call(ctx, to_data, obj, 1, args); if (!JS_IsException(extra) && JS_IsObject(extra)) { JSPropertyEnum *tab; uint32_t len; if (JS_GetOwnPropertyNames(ctx, &tab, &len, extra, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) >= 0) { for (uint32_t i = 0; i < len; i++) { JSValue val = JS_GetProperty(ctx, extra, tab[i].atom); JS_SetProperty(ctx, result, tab[i].atom, val); JS_FreeAtom(ctx, tab[i].atom); } js_free(ctx, tab); } } JS_FreeValue(ctx, extra); } JS_FreeValue(ctx, to_data); return result; } /* ============================================================================ * length() function * ============================================================================ */ static JSValue js_cell_length(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; JSValue val = argv[0]; /* null returns null */ if (JS_IsNull(val)) return JS_NULL; /* Functions return arity (accessed directly, not via properties) */ if (JS_IsFunction(ctx, val)) { JSObject *p = JS_VALUE_GET_OBJ(val); switch (p->class_id) { case JS_CLASS_BYTECODE_FUNCTION: return JS_NewInt32(ctx, p->u.func.function_bytecode->defined_arg_count); case JS_CLASS_C_FUNCTION: return JS_NewInt32(ctx, p->u.cfunc.length); case JS_CLASS_C_FUNCTION_DATA: return JS_NewInt32(ctx, p->u.c_function_data_record->length); default: return JS_NewInt32(ctx, 0); } } int tag = JS_VALUE_GET_TAG(val); /* Strings return codepoint count */ if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE) { JSString *p = JS_VALUE_GET_STRING(val); return JS_NewInt32(ctx, p->len); } /* Check for blob */ blob *bd = js_get_blob(ctx, val); if (bd) return JS_NewInt64(ctx, bd->length); /* Arrays return element count */ if (JS_IsArray(ctx, val)) { int64_t len; if (js_get_length64(ctx, &len, val) < 0) return JS_EXCEPTION; return JS_NewInt64(ctx, len); } /* Objects with length property */ if (JS_IsObject(val)) { JSValue len = JS_GetPropertyStr(ctx, val, "length"); if (!JS_IsException(len) && !JS_IsNull(len)) { if (JS_IsFunction(ctx, len)) { JSValue result = JS_Call(ctx, len, val, 0, NULL); JS_FreeValue(ctx, len); return result; } if (JS_VALUE_IS_NUMBER(len)) return len; JS_FreeValue(ctx, len); } else if (JS_IsException(len)) { return len; } } return JS_NULL; } /* ============================================================================ * call() function - call a function with explicit this and arguments * ============================================================================ */ /* call(func, this_val, ...args) */ static JSValue js_cell_call(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "call requires a function argument"); JSValue func = argv[0]; if (!JS_IsFunction(ctx, func)) return JS_ThrowTypeError(ctx, "first argument must be a function"); JSValue this_arg = JS_NULL; if (argc >= 2) this_arg = argv[1]; int call_argc = argc > 2 ? argc - 2 : 0; JSValueConst *call_argv = call_argc > 0 ? &argv[2] : NULL; return JS_Call(ctx, func, this_arg, call_argc, call_argv); } /* ============================================================================ * is_* type checking functions * ============================================================================ */ /* is_array(val) */ static JSValue js_cell_is_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool(ctx, JS_IsArray(ctx, argv[0])); } /* is_blob(val) */ static JSValue js_cell_is_blob(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool(ctx, js_get_blob(ctx, argv[0]) != NULL); } /* is_data(val) - check if object is a plain object (data record) */ static JSValue js_cell_is_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; JSValue val = argv[0]; if (!JS_IsObject(val)) return JS_FALSE; if (JS_IsArray(ctx, val)) return JS_FALSE; if (JS_IsFunction(ctx, val)) return JS_FALSE; if (js_get_blob(ctx, val)) return JS_FALSE; /* Check if it's a plain object (prototype is Object.prototype or null) */ return JS_TRUE; } /* is_function(val) */ static JSValue js_cell_is_function(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool(ctx, JS_IsFunction(ctx, argv[0])); } /* is_logical(val) - check if value is a boolean (true or false) */ static JSValue js_cell_is_logical(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_BOOL); } /* is_integer(val) */ static JSValue js_cell_is_integer(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; JSValue val = argv[0]; int tag = JS_VALUE_GET_TAG(val); if (tag == JS_TAG_INT) return JS_TRUE; if (tag == JS_TAG_FLOAT64) { double d = JS_VALUE_GET_FLOAT64(val); return JS_NewBool(ctx, isfinite(d) && trunc(d) == d); } return JS_FALSE; } /* is_null(val) */ static JSValue js_cell_is_null(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool(ctx, JS_IsNull(argv[0])); } /* is_number(val) */ static JSValue js_cell_is_number(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool(ctx, JS_IsNumber(argv[0])); } /* is_object(val) - true for non-array, non-null objects */ static JSValue js_cell_is_object(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; JSValue val = argv[0]; if (!JS_IsObject(val)) return JS_FALSE; if (JS_IsArray(ctx, val)) return JS_FALSE; return JS_TRUE; } /* is_stone(val) - check if value is immutable */ static JSValue js_cell_is_stone(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; JSValue val = argv[0]; /* Check blob */ blob *bd = js_get_blob(ctx, val); if (bd) return JS_NewBool(ctx, bd->is_stone); /* Primitives are always stone */ if (!JS_IsObject(val)) return JS_TRUE; /* Check frozen */ return JS_NewBool(ctx, JS_IsExtensible(ctx, val) == 0); } /* is_text(val) */ static JSValue js_cell_is_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_FALSE; int tag = JS_VALUE_GET_TAG(argv[0]); return JS_NewBool(ctx, tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE); } /* is_proto(val, master) - check if val has master in prototype chain */ static JSValue js_cell_is_proto(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_FALSE; JSValue val = argv[0]; JSValue master = argv[1]; if (!JS_IsObject(val) || JS_IsNull(master)) return JS_FALSE; /* Walk prototype chain */ JSValue proto = JS_GetPrototype(ctx, val); while (!JS_IsNull(proto) && !JS_IsException(proto)) { /* If master is a function with prototype property, check that */ if (JS_IsFunction(ctx, master)) { JSValue master_proto = JS_GetPropertyStr(ctx, master, "prototype"); if (!JS_IsException(master_proto) && !JS_IsNull(master_proto)) { JSObject *p1 = JS_VALUE_GET_OBJ(proto); JSObject *p2 = JS_VALUE_GET_OBJ(master_proto); JS_FreeValue(ctx, master_proto); if (p1 == p2) { JS_FreeValue(ctx, proto); return JS_TRUE; } } else if (!JS_IsException(master_proto)) { JS_FreeValue(ctx, master_proto); } } /* Also check if proto == master directly */ if (JS_IsObject(master)) { JSObject *p1 = JS_VALUE_GET_OBJ(proto); JSObject *p2 = JS_VALUE_GET_OBJ(master); if (p1 == p2) { JS_FreeValue(ctx, proto); return JS_TRUE; } } JSValue next = JS_GetPrototype(ctx, proto); JS_FreeValue(ctx, proto); proto = next; } if (JS_IsException(proto)) return proto; return JS_FALSE; } /* eval */ void JS_AddIntrinsicEval(JSContext *ctx) { ctx->eval_internal = __JS_EvalInternal; } static const char * const native_error_name[JS_NATIVE_ERROR_COUNT] = { "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "InternalError", "AggregateError", }; /* Minimum amount of objects to be able to compile code and display error messages. No JSAtom should be allocated by this function. */ static void JS_AddIntrinsicBasicObjects(JSContext *ctx) { JSValue proto; int i; ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto(ctx, JS_NULL); JS_SetImmutablePrototype(ctx, ctx->class_proto[JS_CLASS_OBJECT]); ctx->function_proto = JS_NewCFunction3(ctx, js_function_proto, "", 0, JS_CFUNC_generic, 0, ctx->class_proto[JS_CLASS_OBJECT]); ctx->class_proto[JS_CLASS_BYTECODE_FUNCTION] = JS_DupValue(ctx, ctx->function_proto); ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ERROR], js_error_proto_funcs, countof(js_error_proto_funcs)); for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { proto = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ERROR]); JS_DefinePropertyValue(ctx, proto, JS_ATOM_name, JS_NewAtomString(ctx, native_error_name[i]), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValue(ctx, proto, JS_ATOM_message, JS_AtomToString(ctx, JS_ATOM_empty_string), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); ctx->native_error_proto[i] = proto; } /* the array prototype is an array */ ctx->class_proto[JS_CLASS_ARRAY] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_ARRAY); ctx->array_shape = js_new_shape2(ctx, get_proto_obj(ctx->class_proto[JS_CLASS_ARRAY]), JS_PROP_INITIAL_HASH_SIZE, 1); add_shape_property(ctx, &ctx->array_shape, NULL, JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH); /* XXX: could test it on first context creation to ensure that no new atoms are created in JS_AddIntrinsicBasicObjects(). It is necessary to avoid useless renumbering of atoms after JS_EvalBinary() if it is done just after JS_AddIntrinsicBasicObjects(). */ // assert(ctx->rt->atom_count == JS_ATOM_END); } void JS_AddIntrinsicBaseObjects(JSContext *ctx) { int i; JSValueConst obj, number_obj; JSValue obj1; ctx->throw_type_error = JS_NewCFunction(ctx, js_throw_type_error, NULL, 0); /* add caller property to throw a TypeError */ JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_caller, JS_NULL, ctx->throw_type_error, ctx->throw_type_error, JS_PROP_HAS_GET | JS_PROP_HAS_SET | JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE); JS_FreeValue(ctx, js_object_seal(ctx, JS_NULL, 1, (JSValueConst *)&ctx->throw_type_error, 1)); ctx->global_obj = JS_NewObject(ctx); ctx->global_var_obj = JS_NewObjectProto(ctx, JS_NULL); /* Object - no constructor, just prototype functions */ JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_OBJECT], js_object_proto_funcs, countof(js_object_proto_funcs)); /* Function - no constructor needed */ ctx->function_ctor = JS_NULL; /* Error */ obj1 = JS_NewCFunctionMagic(ctx, js_error_constructor, "Error", 1, JS_CFUNC_constructor_or_func_magic, -1); JS_NewGlobalCConstructor2(ctx, obj1, "Error", ctx->class_proto[JS_CLASS_ERROR]); /* Used to squelch a -Wcast-function-type warning. */ JSCFunctionType ft = { .generic_magic = js_error_constructor }; for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { JSValue func_obj; int n_args; n_args = 1 + (i == JS_AGGREGATE_ERROR); func_obj = JS_NewCFunction3(ctx, ft.generic, native_error_name[i], n_args, JS_CFUNC_constructor_or_func_magic, i, obj1); JS_NewGlobalCConstructor2(ctx, func_obj, native_error_name[i], ctx->native_error_proto[i]); } /* Array */ JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY], js_array_proto_funcs, countof(js_array_proto_funcs)); obj = JS_NewGlobalCConstructor(ctx, "Array", js_array_constructor, 1, ctx->class_proto[JS_CLASS_ARRAY]); ctx->array_ctor = JS_DupValue(ctx, obj); /* XXX: create auto_initializer */ { /* initialize Array.prototype[Symbol.unscopables] */ static const char unscopables[] = "at" "\0" "copyWithin" "\0" "entries" "\0" "fill" "\0" "find" "\0" "findIndex" "\0" "findLast" "\0" "findLastIndex" "\0" "flat" "\0" "flatMap" "\0" "includes" "\0" "keys" "\0" "toReversed" "\0" "toSorted" "\0" "toSpliced" "\0" "values" "\0"; const char *p = unscopables; obj1 = JS_NewObjectProto(ctx, JS_NULL); for(p = unscopables; *p; p += strlen(p) + 1) { JS_DefinePropertyValueStr(ctx, obj1, p, JS_TRUE, JS_PROP_C_W_E); } JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_Symbol_unscopables, obj1, JS_PROP_CONFIGURABLE); } ctx->array_proto_values = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_values); /* String - no constructor, just use text() global function */ ctx->class_proto[JS_CLASS_STRING] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_STRING); /* Symbol - no constructor, but keep symbol type for object keys */ ctx->class_proto[JS_CLASS_SYMBOL] = JS_NewObject(ctx); /* global properties */ JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis, JS_DupValue(ctx, ctx->global_obj), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); /* Cell Script global functions: text, number, array, object, fn */ { JSValue text_func = JS_NewCFunction(ctx, js_cell_text, "text", 2); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "text", text_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue number_func = JS_NewCFunction(ctx, js_cell_number, "number", 2); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "number", number_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue array_func = JS_NewCFunction(ctx, js_cell_array, "array", 4); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "array", array_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue object_func = JS_NewCFunction(ctx, js_cell_object, "object", 2); JS_SetPropertyFunctionList(ctx, object_func, js_cell_object_funcs, countof(js_cell_object_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "object", object_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue fn_obj = JS_NewObject(ctx); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "fn", fn_obj, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* Blob intrinsic type */ { JSClassDef blob_class = { .class_name = "blob", .finalizer = js_blob_finalizer, }; JS_NewClass(JS_GetRuntime(ctx), JS_CLASS_BLOB, &blob_class); ctx->class_proto[JS_CLASS_BLOB] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BLOB], js_blob_proto_funcs, countof(js_blob_proto_funcs)); JSValue blob_ctor = JS_NewCFunction2(ctx, js_blob_constructor, "blob", 3, JS_CFUNC_generic, 0); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "blob", blob_ctor, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } /* stone() function */ JSValue stone_func = JS_NewCFunction(ctx, js_cell_stone, "stone", 1); JS_SetPropertyFunctionList(ctx, stone_func, js_cell_stone_funcs, countof(js_cell_stone_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "stone", stone_func, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* length() function */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "length", JS_NewCFunction(ctx, js_cell_length, "length", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* call() function */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "call", JS_NewCFunction(ctx, js_cell_call, "call", 3), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* is_* type checking functions */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_array", JS_NewCFunction(ctx, js_cell_is_array, "is_array", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_blob", JS_NewCFunction(ctx, js_cell_is_blob, "is_blob", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_data", JS_NewCFunction(ctx, js_cell_is_data, "is_data", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_function", JS_NewCFunction(ctx, js_cell_is_function, "is_function", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_logical", JS_NewCFunction(ctx, js_cell_is_logical, "is_logical", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_integer", JS_NewCFunction(ctx, js_cell_is_integer, "is_integer", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_null", JS_NewCFunction(ctx, js_cell_is_null, "is_null", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_number", JS_NewCFunction(ctx, js_cell_is_number, "is_number", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_object", JS_NewCFunction(ctx, js_cell_is_object, "is_object", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_stone", JS_NewCFunction(ctx, js_cell_is_stone, "is_stone", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_text", JS_NewCFunction(ctx, js_cell_is_text, "is_text", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "is_proto", JS_NewCFunction(ctx, js_cell_is_proto, "is_proto", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "apply", JS_NewCFunction(ctx, js_cell_fn_apply, "apply", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "replace", JS_NewCFunction(ctx, js_cell_text_replace, "replace", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "lower", JS_NewCFunction(ctx, js_cell_text_lower, "lower", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "upper", JS_NewCFunction(ctx, js_cell_text_upper, "upper", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "trim", JS_NewCFunction(ctx, js_cell_text_trim, "trim", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "codepoint", JS_NewCFunction(ctx, js_cell_text_codepoint, "codepoint", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "search", JS_NewCFunction(ctx, js_cell_text_search, "search", 3), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "extract", JS_NewCFunction(ctx, js_cell_text_extract, "extract", 3), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "reduce", JS_NewCFunction(ctx, js_cell_array_reduce, "reduce", 4), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "arrfor", JS_NewCFunction(ctx, js_cell_array_for, "for", 4), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "find", JS_NewCFunction(ctx, js_cell_array_find, "find", 4), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "filter", JS_NewCFunction(ctx, js_cell_array_filter, "filter", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "sort", JS_NewCFunction(ctx, js_cell_array_sort, "sort", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* Number utility functions as globals */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "whole", JS_NewCFunction(ctx, js_cell_number_whole, "whole", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "fraction", JS_NewCFunction(ctx, js_cell_number_fraction, "fraction", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "floor", JS_NewCFunction(ctx, js_cell_number_floor, "floor", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "ceiling", JS_NewCFunction(ctx, js_cell_number_ceiling, "ceiling", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "abs", JS_NewCFunction(ctx, js_cell_number_abs, "abs", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "round", JS_NewCFunction(ctx, js_cell_number_round, "round", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "sign", JS_NewCFunction(ctx, js_cell_number_sign, "sign", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "trunc", JS_NewCFunction(ctx, js_cell_number_trunc, "trunc", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "min", JS_NewCFunction(ctx, js_cell_number_min, "min", 0), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "max", JS_NewCFunction(ctx, js_cell_number_max, "max", 0), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "remainder", JS_NewCFunction(ctx, js_cell_number_remainder, "remainder", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "character", JS_NewCFunction(ctx, js_cell_character, "character", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* modulo(dividend, divisor) - dividend - (divisor * floor(dividend / divisor)) */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "modulo", JS_NewCFunction(ctx, js_cell_modulo, "modulo", 2), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* neg(number) - negate a number */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "neg", JS_NewCFunction(ctx, js_cell_neg, "neg", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* not(boolean) - logical not */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "not", JS_NewCFunction(ctx, js_cell_not, "not", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* reverse() - reverse an array */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "reverse", JS_NewCFunction(ctx, js_cell_reverse, "reverse", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* proto() - get prototype of an object */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "proto", JS_NewCFunction(ctx, js_cell_proto, "proto", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* splat() - flatten object with prototype chain */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "splat", JS_NewCFunction(ctx, js_cell_splat, "splat", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* splat() - flatten object with prototype chain */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "meme", JS_NewCFunction(ctx, js_cell_meme, "meme", 1), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* pi - mathematical constant */ JS_DefinePropertyValueStr(ctx, ctx->global_obj, "pi", JS_NewFloat64(ctx, 3.14159265358979323846264338327950288419716939937510), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } } #define STRLEN(s) (sizeof(s)/sizeof(s[0])) #define CSTR "" void js_debug_info(JSContext *js, JSValue fn, js_debug *dbg) { memset(dbg, 0, sizeof(*dbg)); if (!JS_IsObject(fn)) return; JSObject *p = JS_VALUE_GET_OBJ(fn); const char *fn_name = get_prop_string(js, fn, JS_ATOM_name); if (!fn_name) dbg->name = js_strdup(js, ""); else if (fn_name[0] == 0) { dbg->name = js_strdup(js, ""); JS_FreeCString(js,fn_name); } else { dbg->name = js_strdup(js, fn_name); JS_FreeCString(js,fn_name); } dbg->unique = (int)p; switch(p->class_id) { case JS_CLASS_BYTECODE_FUNCTION: { JSFunctionBytecode *b = p->u.func.function_bytecode; // get filename const char *filename = JS_AtomToCString(js, b->debug.filename); if (!filename) dbg->filename = js_strdup(js, "unknown"); else if (filename[0] == 0) { dbg->filename = js_strdup(js, "unknown"); JS_FreeCString(js,filename); } else { dbg->filename = js_strdup(js, filename); JS_FreeCString(js,filename); } dbg->what = "JS"; dbg->closure_n = b->closure_var_count; dbg->param_n = b->arg_count; dbg->vararg = 1; int pcol_num; dbg->line = find_line_num(js, b, -1, &pcol_num); dbg->source = b->debug.source; dbg->srclen = b->debug.source_len; break; } case JS_CLASS_C_FUNCTION: dbg->filename = js_strdup(js, ""); dbg->what = "C"; dbg->nparams = 0; dbg->vararg = 1; dbg->line = 0; dbg->source = CSTR; dbg->srclen = STRLEN(CSTR); break; } } void free_js_debug_info(JSContext *js, js_debug *dbg) { js_free(js, dbg->filename); js_free(js, dbg->name); } void js_debug_sethook(JSContext *ctx, js_hook hook, int type, void *user) { ctx->trace_hook = hook; ctx->trace_type = type; ctx->trace_data = user; } uint32_t js_debugger_stack_depth(JSContext *ctx) { uint32_t stack_index = 0; JSStackFrame *sf = ctx->rt->current_stack_frame; while (sf != NULL) { sf = sf->prev_frame; stack_index++; } return stack_index; } JSValue js_debugger_backtrace_fns(JSContext *ctx, const uint8_t *cur_pc) { JSValue ret = JS_NewArray(ctx); JSStackFrame *sf; uint32_t stack_index = 0; for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { uint32_t id = stack_index++; JS_SetPropertyUint32(ctx, ret, id, JS_DupValue(ctx,sf->cur_func)); } return ret; } JSValue js_debugger_build_backtrace(JSContext *ctx, const uint8_t *cur_pc) { JSStackFrame *sf; const char *func_name_str; JSObject *p; JSValue ret = JS_NewArray(ctx); uint32_t stack_index = 0; for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { JSValue current_frame = JS_NewObject(ctx); uint32_t id = stack_index++; JS_SetPropertyStr(ctx, current_frame, "id", JS_NewUint32(ctx, id)); func_name_str = get_func_name(ctx, sf->cur_func); if (!func_name_str || func_name_str[0] == '\0') JS_SetPropertyStr(ctx, current_frame, "name", JS_NewString(ctx, "")); else JS_SetPropertyStr(ctx, current_frame, "name", JS_NewString(ctx, func_name_str)); JS_FreeCString(ctx, func_name_str); p = JS_VALUE_GET_OBJ(sf->cur_func); if (p && js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b; int line_num1; b = p->u.func.function_bytecode; if (b->has_debug) { const uint8_t *pc = sf != ctx->rt->current_stack_frame || !cur_pc ? sf->cur_pc : cur_pc; int col_num; line_num1 = find_line_num(ctx, b, pc - b->byte_code_buf - 1, &col_num); JS_SetPropertyStr(ctx, current_frame, "filename", JS_AtomToString(ctx, b->debug.filename)); if (line_num1 != -1) JS_SetPropertyStr(ctx, current_frame, "line", JS_NewUint32(ctx, line_num1)); } } else { JS_SetPropertyStr(ctx, current_frame, "name", JS_NewString(ctx, "(native)")); } JS_SetPropertyUint32(ctx, ret, id, current_frame); } return ret; } JSValue js_debugger_fn_info(JSContext *ctx, JSValue fn) { JSValue ret = JS_NewObject(ctx); JSObject *f = JS_VALUE_GET_OBJ(fn); if (!f || !js_class_has_bytecode(f->class_id)) goto done; JSFunctionBytecode *b = f->u.func.function_bytecode; char atom_buf[ATOM_GET_STR_BUF_SIZE]; const char *str; int i; // Function name if (b->func_name != JS_ATOM_NULL) { str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name); JS_SetPropertyStr(ctx, ret, "name", JS_NewString(ctx, str)); } // File location info if (b->has_debug && b->debug.filename != JS_ATOM_NULL) { int line_num, col_num; str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->debug.filename); line_num = find_line_num(ctx, b, -1, &col_num); JS_SetPropertyStr(ctx, ret, "filename", JS_NewString(ctx, str)); JS_SetPropertyStr(ctx, ret, "line", JS_NewInt32(ctx, line_num)); JS_SetPropertyStr(ctx, ret, "column", JS_NewInt32(ctx, col_num)); } // Arguments if (b->arg_count && b->vardefs) { JSValue args_array = JS_NewArray(ctx); for (i = 0; i < b->arg_count; i++) { str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->vardefs[i].var_name); JS_SetPropertyUint32(ctx, args_array, i, JS_NewString(ctx, str)); } JS_SetPropertyStr(ctx, ret, "args", args_array); } // Local variables if (b->var_count && b->vardefs) { JSValue locals_array = JS_NewArray(ctx); for (i = 0; i < b->var_count; i++) { JSVarDef *vd = &b->vardefs[b->arg_count + i]; JSValue local_obj = JS_NewObject(ctx); str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), vd->var_name); JS_SetPropertyStr(ctx, local_obj, "name", JS_NewString(ctx, str)); const char *var_type = vd->var_kind == JS_VAR_CATCH ? "catch" : (vd->var_kind == JS_VAR_FUNCTION_DECL || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" : vd->is_const ? "const" : vd->is_lexical ? "let" : "var"; JS_SetPropertyStr(ctx, local_obj, "type", JS_NewString(ctx, var_type)); JS_SetPropertyStr(ctx, local_obj, "index", JS_NewInt32(ctx, i)); if (vd->scope_level) { JS_SetPropertyStr(ctx, local_obj, "scope_level", JS_NewInt32(ctx, vd->scope_level)); JS_SetPropertyStr(ctx, local_obj, "scope_next", JS_NewInt32(ctx, vd->scope_next)); } JS_SetPropertyUint32(ctx, locals_array, i, local_obj); } JS_SetPropertyStr(ctx, ret, "locals", locals_array); } // Closure variables if (b->closure_var_count) { JSValue closure_array = JS_NewArray(ctx); for (i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; JSValue closure_obj = JS_NewObject(ctx); str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), cv->var_name); JS_SetPropertyStr(ctx, closure_obj, "name", JS_NewString(ctx, str)); JS_SetPropertyStr(ctx, closure_obj, "is_local", JS_NewBool(ctx, cv->is_local)); JS_SetPropertyStr(ctx, closure_obj, "is_arg", JS_NewBool(ctx, cv->is_arg)); JS_SetPropertyStr(ctx, closure_obj, "var_idx", JS_NewInt32(ctx, cv->var_idx)); const char *var_type = cv->is_const ? "const" : cv->is_lexical ? "let" : "var"; JS_SetPropertyStr(ctx, closure_obj, "type", JS_NewString(ctx, var_type)); JS_SetPropertyUint32(ctx, closure_array, i, closure_obj); } JS_SetPropertyStr(ctx, ret, "closure_vars", closure_array); } // Stack size JS_SetPropertyStr(ctx, ret, "stack_size", JS_NewInt32(ctx, b->stack_size)); done: return ret; } // Opcode names array for debugger static const char *opcode_names[] = { #define FMT(f) #define DEF(id, size, n_pop, n_push, f) #id, #define def(id, size, n_pop, n_push, f) #include "quickjs-opcode.h" #undef def #undef DEF #undef FMT }; JSValue js_debugger_fn_bytecode(JSContext *ctx, JSValue fn) { JSObject *f = JS_VALUE_GET_OBJ(fn); if (!f || !js_class_has_bytecode(f->class_id)) return JS_NULL; JSFunctionBytecode *b = f->u.func.function_bytecode; JSValue ret = JS_NewArray(ctx); const uint8_t *tab = b->byte_code_buf; int len = b->byte_code_len; int pos = 0; int idx = 0; BOOL use_short_opcodes = TRUE; char opcode_str[256]; while (pos < len) { int op = tab[pos]; const JSOpCode *oi; if (use_short_opcodes) oi = &short_opcode_info(op); else oi = &opcode_info[op]; int size = oi->size; if (pos + size > len) { break; } if (op >= sizeof(opcode_names)/sizeof(opcode_names[0])) { snprintf(opcode_str, sizeof(opcode_str), "unknown"); } else { const char *opcode_name = opcode_names[op]; snprintf(opcode_str, sizeof(opcode_str), "%s", opcode_name); // Add arguments based on opcode format int arg_pos = pos + 1; switch(oi->fmt) { case OP_FMT_none_int: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", op - OP_push_0); break; case OP_FMT_npopx: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", op - OP_call0); break; case OP_FMT_u8: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_u8(tab + arg_pos)); break; case OP_FMT_i8: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", get_i8(tab + arg_pos)); break; case OP_FMT_u16: case OP_FMT_npop: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_u16(tab + arg_pos)); break; case OP_FMT_npop_u16: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u %u", get_u16(tab + arg_pos), get_u16(tab + arg_pos + 2)); break; case OP_FMT_i16: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", get_i16(tab + arg_pos)); break; case OP_FMT_i32: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", get_i32(tab + arg_pos)); break; case OP_FMT_u32: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_u32(tab + arg_pos)); break; #if SHORT_OPCODES case OP_FMT_label8: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_i8(tab + arg_pos) + arg_pos); break; case OP_FMT_label16: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_i16(tab + arg_pos) + arg_pos); break; case OP_FMT_const8: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_u8(tab + arg_pos)); break; #endif case OP_FMT_const: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_u32(tab + arg_pos)); break; case OP_FMT_label: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", get_u32(tab + arg_pos) + arg_pos); break; case OP_FMT_label_u16: snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u %u", get_u32(tab + arg_pos) + arg_pos, get_u16(tab + arg_pos + 4)); break; case OP_FMT_atom: { JSAtom atom = get_u32(tab + arg_pos); const char *atom_str = JS_AtomToCString(ctx, atom); if (atom_str) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %s", atom_str); JS_FreeCString(ctx, atom_str); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", atom); } } break; case OP_FMT_atom_u8: { JSAtom atom = get_u32(tab + arg_pos); const char *atom_str = JS_AtomToCString(ctx, atom); if (atom_str) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %s %d", atom_str, get_u8(tab + arg_pos + 4)); JS_FreeCString(ctx, atom_str); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u %d", atom, get_u8(tab + arg_pos + 4)); } } break; case OP_FMT_atom_u16: { JSAtom atom = get_u32(tab + arg_pos); const char *atom_str = JS_AtomToCString(ctx, atom); if (atom_str) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %s %d", atom_str, get_u16(tab + arg_pos + 4)); JS_FreeCString(ctx, atom_str); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u %d", atom, get_u16(tab + arg_pos + 4)); } } break; case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: { JSAtom atom = get_u32(tab + arg_pos); int addr = get_u32(tab + arg_pos + 4); int extra = (oi->fmt == OP_FMT_atom_label_u8) ? get_u8(tab + arg_pos + 8) : get_u16(tab + arg_pos + 8); const char *atom_str = JS_AtomToCString(ctx, atom); if (atom_str) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %s %u %u", atom_str, addr + arg_pos + 4, extra); JS_FreeCString(ctx, atom_str); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u %u %u", atom, addr + arg_pos + 4, extra); } } break; case OP_FMT_none_loc: { int idx = (op - OP_get_loc0) % 4; if (idx < b->var_count) { const char *var_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name); if (var_name) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d: %s", idx, var_name); JS_FreeCString(ctx, var_name); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", idx); } } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", idx); } } break; case OP_FMT_loc8: { int idx = get_u8(tab + arg_pos); if (idx < b->var_count) { const char *var_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name); if (var_name) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u: %s", idx, var_name); JS_FreeCString(ctx, var_name); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } break; case OP_FMT_loc: { int idx = get_u16(tab + arg_pos); if (idx < b->var_count) { const char *var_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name); if (var_name) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u: %s", idx, var_name); JS_FreeCString(ctx, var_name); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } break; case OP_FMT_none_arg: { int idx = (op - OP_get_arg0) % 4; if (idx < b->arg_count) { const char *arg_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name); if (arg_name) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d: %s", idx, arg_name); JS_FreeCString(ctx, arg_name); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", idx); } } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", idx); } } break; case OP_FMT_arg: { int idx = get_u16(tab + arg_pos); if (idx < b->arg_count) { const char *arg_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name); if (arg_name) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u: %s", idx, arg_name); JS_FreeCString(ctx, arg_name); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } break; case OP_FMT_none_var_ref: { int idx = (op - OP_get_var_ref0) % 4; if (idx < b->closure_var_count) { const char *var_name = JS_AtomToCString(ctx, b->closure_var[idx].var_name); if (var_name) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d: %s", idx, var_name); JS_FreeCString(ctx, var_name); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", idx); } } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %d", idx); } } break; case OP_FMT_var_ref: { int idx = get_u16(tab + arg_pos); if (idx < b->closure_var_count) { const char *var_name = JS_AtomToCString(ctx, b->closure_var[idx].var_name); if (var_name) { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u: %s", idx, var_name); JS_FreeCString(ctx, var_name); } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } else { snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str), " %u", idx); } } break; default: break; } } JSValue js_opcode_str = JS_NewString(ctx, opcode_str); JS_SetPropertyUint32(ctx, ret, idx++, js_opcode_str); pos += size; } return ret; } JSValue js_debugger_local_variables(JSContext *ctx, int stack_index) { JSValue ret = JS_NewObject(ctx); JSStackFrame *sf; int cur_index = 0; for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { // this val is one frame up if (cur_index == stack_index - 1) { JSObject *f = JS_VALUE_GET_OBJ(sf->cur_func); if (f && js_class_has_bytecode(f->class_id)) { JSFunctionBytecode *b = f->u.func.function_bytecode; JSValue this_obj = sf->var_buf[b->var_count]; // only provide a this if it is not the global object. if (JS_VALUE_GET_OBJ(this_obj) != JS_VALUE_GET_OBJ(ctx->global_obj)) JS_SetPropertyStr(ctx, ret, "this", JS_DupValue(ctx, this_obj)); } } if (cur_index < stack_index) { cur_index++; continue; } JSObject *f = JS_VALUE_GET_OBJ(sf->cur_func); if (!f || !js_class_has_bytecode(f->class_id)) goto done; JSFunctionBytecode *b = f->u.func.function_bytecode; for (uint32_t i = 0; i < b->arg_count + b->var_count; i++) { JSValue var_val; if (i < b->arg_count) var_val = sf->arg_buf[i]; else var_val = sf->var_buf[i - b->arg_count]; if (JS_IsUninitialized(var_val)) continue; JSVarDef *vd = b->vardefs + i; JS_SetProperty(ctx, ret, vd->var_name, JS_DupValue(ctx, var_val)); } break; } done: return ret; } void js_debugger_set_closure_variable(JSContext *ctx, JSValue fn, JSValue var_name, JSValue val) { JSObject *f = JS_VALUE_GET_OBJ(fn); if (!f || !js_class_has_bytecode(f->class_id)) return; JSFunctionBytecode *b = f->u.func.function_bytecode; const char *name_str = JS_ToCString(ctx, var_name); if (!name_str) return; for (uint32_t i = 0; i < b->closure_var_count; i++) { JSClosureVar *cvar = b->closure_var + i; const char *cvar_name = JS_AtomToCString(ctx, cvar->var_name); if (!cvar_name) continue; if (strcmp(name_str, cvar_name) == 0) { JS_FreeCString(ctx, cvar_name); JSVarRef *var_ref = NULL; if (f->u.func.var_refs) var_ref = f->u.func.var_refs[i]; if (var_ref && var_ref->pvalue) { JS_FreeValue(ctx, *var_ref->pvalue); *var_ref->pvalue = JS_DupValue(ctx, val); } break; } JS_FreeCString(ctx, cvar_name); } JS_FreeCString(ctx, name_str); } JSValue js_debugger_closure_variables(JSContext *ctx, JSValue fn) { JSValue ret = JS_NewObject(ctx); JSObject *f = JS_VALUE_GET_OBJ(fn); if (!f || !js_class_has_bytecode(f->class_id)) goto done; JSFunctionBytecode *b = f->u.func.function_bytecode; for (uint32_t i = 0; i < b->closure_var_count; i++) { JSClosureVar *cvar = b->closure_var + i; JSValue var_val; JSVarRef *var_ref = NULL; if (f->u.func.var_refs) var_ref = f->u.func.var_refs[i]; if (!var_ref || !var_ref->pvalue) continue; var_val = *var_ref->pvalue; if (JS_IsUninitialized(var_val)) continue; JS_SetProperty(ctx, ret, cvar->var_name, JS_DupValue(ctx, var_val)); } done: return ret; } void *js_debugger_val_address(JSContext *ctx, JSValue val) { return JS_VALUE_GET_PTR(val); } /* ============================================================================ * Cell Script Module: json * Provides json.encode() and json.decode() using pure C implementation * ============================================================================ */ static JSValue js_cell_json_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "json.encode requires at least 1 argument"); JSValue replacer = argc > 1 ? argv[1] : JS_NULL; JSValue space = argc > 2 ? argv[2] : JS_NewInt32(ctx, 1); JSValue result = JS_JSONStringify(ctx, argv[0], replacer, space); if (argc <= 2) JS_FreeValue(ctx, space); return result; } static JSValue js_cell_json_decode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "json.decode requires at least 1 argument"); int tag = JS_VALUE_GET_TAG(argv[0]); if (tag != JS_TAG_STRING && tag != JS_TAG_STRING_ROPE) { JSValue err = JS_NewError(ctx); JS_DefinePropertyValueStr(ctx, err, "message", JS_NewString(ctx, "couldn't parse text: not a string"), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); return JS_Throw(ctx, err); } const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; size_t len = strlen(str); JSValue result = JS_ParseJSON(ctx, str, len, ""); JS_FreeCString(ctx, str); /* Apply reviver if provided */ if (argc > 1 && JS_IsFunction(ctx, argv[1]) && !JS_IsException(result)) { /* Create wrapper object to pass to reviver */ JSValue wrapper = JS_NewObject(ctx); JS_DefinePropertyValueStr(ctx, wrapper, "", result, JS_PROP_C_W_E); JSValue holder = wrapper; JSAtom key = JS_NewAtom(ctx, ""); JSValue args[2] = { JS_AtomToString(ctx, key), JS_GetProperty(ctx, holder, key) }; JSValue final = JS_Call(ctx, argv[1], holder, 2, args); JS_FreeValue(ctx, args[0]); JS_FreeValue(ctx, args[1]); JS_FreeAtom(ctx, key); JS_FreeValue(ctx, wrapper); result = final; } return result; } static const JSCFunctionListEntry js_cell_json_funcs[] = { JS_CFUNC_DEF("encode", 1, js_cell_json_encode), JS_CFUNC_DEF("decode", 1, js_cell_json_decode), }; JSValue js_json_use(JSContext *ctx) { JSValue obj = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, obj, js_cell_json_funcs, countof(js_cell_json_funcs)); return obj; } static JSValue js_math_e(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double power = 1.0; if (argc > 0 && !JS_IsNull(argv[0])) { if (JS_ToFloat64(ctx, &power, argv[0]) < 0) return JS_EXCEPTION; } return JS_NewFloat64(ctx, exp(power)); } /* ============================================================================ * Cell Script Module: math/radians * Provides trigonometric and math functions using radians * ============================================================================ */ static JSValue js_math_rad_arc_cosine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, acos(x)); } static JSValue js_math_rad_arc_sine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, asin(x)); } static JSValue js_math_rad_arc_tangent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, atan(x)); } static JSValue js_math_rad_cosine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, cos(x)); } static JSValue js_math_rad_sine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, sin(x)); } static JSValue js_math_rad_tangent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, tan(x)); } static JSValue js_math_ln(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, log(x)); } static JSValue js_math_log10(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, log10(x)); } static JSValue js_math_log2(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, log2(x)); } static JSValue js_math_power(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x, y; if (argc < 2) return JS_NULL; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToFloat64(ctx, &y, argv[1]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, pow(x, y)); } static JSValue js_math_root(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x, n; if (argc < 2) return JS_NULL; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToFloat64(ctx, &n, argv[1]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, pow(x, 1.0 / n)); } static JSValue js_math_sqrt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, sqrt(x)); } static const JSCFunctionListEntry js_math_radians_funcs[] = { JS_CFUNC_DEF("arc_cosine", 1, js_math_rad_arc_cosine), JS_CFUNC_DEF("arc_sine", 1, js_math_rad_arc_sine), JS_CFUNC_DEF("arc_tangent", 1, js_math_rad_arc_tangent), JS_CFUNC_DEF("cosine", 1, js_math_rad_cosine), JS_CFUNC_DEF("sine", 1, js_math_rad_sine), JS_CFUNC_DEF("tangent", 1, js_math_rad_tangent), JS_CFUNC_DEF("ln", 1, js_math_ln), JS_CFUNC_DEF("log", 1, js_math_log10), JS_CFUNC_DEF("log2", 1, js_math_log2), JS_CFUNC_DEF("power", 2, js_math_power), JS_CFUNC_DEF("root", 2, js_math_root), JS_CFUNC_DEF("sqrt", 1, js_math_sqrt), JS_CFUNC_DEF("e", 1, js_math_e) }; JSValue js_math_radians_use(JSContext *ctx) { JSValue obj = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, obj, js_math_radians_funcs, countof(js_math_radians_funcs)); return obj; } /* ============================================================================ * Cell Script Module: math/degrees * Provides trigonometric and math functions using degrees * ============================================================================ */ #define DEG2RAD (3.14159265358979323846264338327950288419716939937510 / 180.0) #define RAD2DEG (180.0 / 3.14159265358979323846264338327950288419716939937510) static JSValue js_math_deg_arc_cosine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, acos(x) * RAD2DEG); } static JSValue js_math_deg_arc_sine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, asin(x) * RAD2DEG); } static JSValue js_math_deg_arc_tangent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, atan(x) * RAD2DEG); } static JSValue js_math_deg_cosine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, cos(x * DEG2RAD)); } static JSValue js_math_deg_sine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, sin(x * DEG2RAD)); } static JSValue js_math_deg_tangent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, tan(x * DEG2RAD)); } static const JSCFunctionListEntry js_math_degrees_funcs[] = { JS_CFUNC_DEF("arc_cosine", 1, js_math_deg_arc_cosine), JS_CFUNC_DEF("arc_sine", 1, js_math_deg_arc_sine), JS_CFUNC_DEF("arc_tangent", 1, js_math_deg_arc_tangent), JS_CFUNC_DEF("cosine", 1, js_math_deg_cosine), JS_CFUNC_DEF("sine", 1, js_math_deg_sine), JS_CFUNC_DEF("tangent", 1, js_math_deg_tangent), JS_CFUNC_DEF("ln", 1, js_math_ln), JS_CFUNC_DEF("log", 1, js_math_log10), JS_CFUNC_DEF("log2", 1, js_math_log2), JS_CFUNC_DEF("power", 2, js_math_power), JS_CFUNC_DEF("root", 2, js_math_root), JS_CFUNC_DEF("sqrt", 1, js_math_sqrt), JS_CFUNC_DEF("e", 1, js_math_e) }; JSValue js_math_degrees_use(JSContext *ctx) { JSValue obj = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, obj, js_math_degrees_funcs, countof(js_math_degrees_funcs)); return obj; } /* ============================================================================ * Cell Script Module: math/cycles * Provides trigonometric and math functions using cycles (0-1 = full rotation) * ============================================================================ */ #define TWOPI (2.0 * 3.14159265358979323846264338327950288419716939937510) static JSValue js_math_cyc_arc_cosine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, acos(x) / TWOPI); } static JSValue js_math_cyc_arc_sine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, asin(x) / TWOPI); } static JSValue js_math_cyc_arc_tangent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, atan(x) / TWOPI); } static JSValue js_math_cyc_cosine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, cos(x * TWOPI)); } static JSValue js_math_cyc_sine(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, sin(x * TWOPI)); } static JSValue js_math_cyc_tangent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double x; if (JS_ToFloat64(ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64(ctx, tan(x * TWOPI)); } static const JSCFunctionListEntry js_math_cycles_funcs[] = { JS_CFUNC_DEF("arc_cosine", 1, js_math_cyc_arc_cosine), JS_CFUNC_DEF("arc_sine", 1, js_math_cyc_arc_sine), JS_CFUNC_DEF("arc_tangent", 1, js_math_cyc_arc_tangent), JS_CFUNC_DEF("cosine", 1, js_math_cyc_cosine), JS_CFUNC_DEF("sine", 1, js_math_cyc_sine), JS_CFUNC_DEF("tangent", 1, js_math_cyc_tangent), JS_CFUNC_DEF("ln", 1, js_math_ln), JS_CFUNC_DEF("log", 1, js_math_log10), JS_CFUNC_DEF("log2", 1, js_math_log2), JS_CFUNC_DEF("power", 2, js_math_power), JS_CFUNC_DEF("root", 2, js_math_root), JS_CFUNC_DEF("sqrt", 1, js_math_sqrt), JS_CFUNC_DEF("e", 1, js_math_e) }; JSValue js_math_cycles_use(JSContext *ctx) { JSValue obj = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, obj, js_math_cycles_funcs, countof(js_math_cycles_funcs)); return obj; } JSContext *JS_GetContext(JSRuntime *rt) { return rt->js; }