/* * QuickJS Javascript Engine * * Copyright (c) 2017-2020 Fabrice Bellard * Copyright (c) 2017-2020 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__) #include #endif #include "cutils.h" #include "list.h" #include "quickjs.h" #include "libregexp.h" #ifdef CONFIG_BIGNUM #include "libbf.h" #endif #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 /* define to include Atomics.* operations which depend on the OS threads */ #if !defined(EMSCRIPTEN) #define CONFIG_ATOMICS #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 */ //#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_MODULE_RESOLVE //#define DUMP_PROMISE //#define DUMP_READ_OBJECT /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC #ifdef CONFIG_ATOMICS #include #include #include #endif 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_ARGUMENTS, /* u.array | length */ JS_CLASS_MAPPED_ARGUMENTS, /* | length */ JS_CLASS_DATE, /* u.object_data */ JS_CLASS_MODULE_NS, 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_GENERATOR_FUNCTION, /* u.func */ JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ JS_CLASS_REGEXP, /* u.regexp */ JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ #ifdef CONFIG_BIGNUM JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ #endif JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ JS_CLASS_DATAVIEW, /* u.typed_array */ #ifdef CONFIG_BIGNUM JS_CLASS_BIG_INT, /* u.object_data */ JS_CLASS_BIG_FLOAT, /* u.object_data */ JS_CLASS_FLOAT_ENV, /* u.float_env */ JS_CLASS_BIG_DECIMAL, /* u.object_data */ JS_CLASS_OPERATOR_SET, /* u.operator_set */ #endif JS_CLASS_MAP, /* u.map_state */ JS_CLASS_SET, /* u.map_state */ JS_CLASS_WEAKMAP, /* u.map_state */ JS_CLASS_WEAKSET, /* u.map_state */ JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ JS_CLASS_GENERATOR, /* u.generator_data */ JS_CLASS_PROXY, /* u.proxy_data */ JS_CLASS_PROMISE, /* u.promise_data */ JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ JS_CLASS_ASYNC_FUNCTION, /* u.func */ JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ }; /* number of typed array types */ #define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; #define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) 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_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ } JSErrorEnum; #define JS_MAX_LOCAL_VARS 65536 #define JS_STACK_SIZE_MAX 65536 #define JS_STRING_LEN_MAX ((1 << 30) - 1) #define __exception __attribute__((warn_unused_result)) typedef struct JSShape JSShape; typedef struct JSString JSString; typedef struct JSString JSAtomStruct; typedef enum { JS_GC_PHASE_NONE, JS_GC_PHASE_DECREF, JS_GC_PHASE_REMOVE_CYCLES, } JSGCPhaseEnum; typedef enum OPCodeEnum OPCodeEnum; #ifdef CONFIG_BIGNUM /* function pointers are used for numeric operations so that it is possible to remove some numeric types */ typedef struct { JSValue (*to_string)(JSContext *ctx, JSValueConst val); JSValue (*from_string)(JSContext *ctx, const char *buf, int radix, int flags, slimb_t *pexponent); int (*unary_arith)(JSContext *ctx, JSValue *pres, OPCodeEnum op, JSValue op1); int (*binary_arith)(JSContext *ctx, OPCodeEnum op, JSValue *pres, JSValue op1, JSValue op2); int (*compare)(JSContext *ctx, OPCodeEnum op, JSValue op1, JSValue op2); /* only for bigfloat: */ JSValue (*mul_pow10_to_float64)(JSContext *ctx, const bf_t *a, int64_t exponent); int (*mul_pow10)(JSContext *ctx, JSValue *sp); } JSNumericOperations; #endif 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 JSInterruptHandler *interrupt_handler; void *interrupt_opaque; JSHostPromiseRejectionTracker *host_promise_rejection_tracker; void *host_promise_rejection_tracker_opaque; struct list_head job_list; /* list of JSJobEntry.link */ JSModuleNormalizeFunc *module_normalize_func; JSModuleLoaderFunc *module_loader_func; void *module_loader_opaque; BOOL can_block : 8; /* TRUE if Atomics.wait can block */ /* Shape hash table */ int shape_hash_bits; int shape_hash_size; int shape_hash_count; /* number of hashed shapes */ JSShape **shape_hash; #ifdef CONFIG_BIGNUM bf_context_t bf_ctx; JSNumericOperations bigint_ops; JSNumericOperations bigfloat_ops; JSNumericOperations bigdecimal_ops; uint32_t operator_count; #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_STRICT (1 << 0) #define JS_MODE_STRIP (1 << 1) #define JS_MODE_MATH (1 << 2) typedef struct JSStackFrame { struct JSStackFrame *prev_frame; /* NULL if first stack frame */ JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ JSValue *arg_buf; /* arguments */ JSValue *var_buf; /* variables */ struct list_head var_ref_list; /* list of JSVarRef.link */ const uint8_t *cur_pc; /* only used in bytecode functions : PC of the instruction after the call */ int arg_count; int js_mode; /* 0 or JS_MODE_MATH for C functions */ /* only used in generators. Current stack pointer value. NULL if the function is running. */ JSValue *cur_sp; } JSStackFrame; 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_ASYNC_FUNCTION, } 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 */ /* 0 : the JSVarRef is on the stack. header.link is an element of JSStackFrame.var_ref_list. 1 : the JSVarRef is detached. header.link has the normal meanning */ uint8_t is_detached : 1; uint8_t is_arg : 1; uint16_t var_idx; /* index of the corresponding function variable on the stack */ }; }; JSValue *pvalue; /* pointer to the value, either on the stack or to 'value' */ JSValue value; /* used when the variable is no longer on the stack */ } JSVarRef; #ifdef CONFIG_BIGNUM typedef struct JSFloatEnv { limb_t prec; bf_flags_t flags; unsigned int status; } JSFloatEnv; /* the same structure is used for big integers and big floats. Big integers are never infinite or NaNs */ typedef struct JSBigFloat { JSRefCountHeader header; /* must come first, 32-bit */ bf_t num; } JSBigFloat; typedef struct JSBigDecimal { JSRefCountHeader header; /* must come first, 32-bit */ bfdec_t num; } JSBigDecimal; #endif /* 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 { JSRuntime *rt; struct list_head link; const uint8_t *stack_top; size_t stack_size; /* in bytes */ JSValue current_exception; /* true if inside an out of memory error, to avoid recursing */ BOOL in_out_of_memory : 8; uint16_t binary_object_count; int binary_object_size; JSShape *array_shape; /* initial shape for Array objects */ JSStackFrame *current_stack_frame; JSValue *class_proto; JSValue function_proto; JSValue function_ctor; JSValue regexp_ctor; JSValue promise_ctor; JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; JSValue iterator_proto; JSValue async_iterator_proto; 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; #ifdef CONFIG_BIGNUM bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */ JSFloatEnv fp_env; /* global FP environment */ BOOL bignum_ext : 8; /* enable math mode */ BOOL allow_operator_overloading : 8; #endif /* when the counter reaches zero, JSRutime.interrupt_handler is called */ int interrupt_counter; BOOL is_error_property_enabled; struct list_head loaded_modules; /* list of JSModuleDef.link */ /* 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; }; typedef union JSFloat64Union { double d; uint64_t u64; uint32_t u32[2]; } JSFloat64Union; enum { JS_ATOM_TYPE_STRING = 1, JS_ATOM_TYPE_GLOBAL_SYMBOL, JS_ATOM_TYPE_SYMBOL, JS_ATOM_TYPE_PRIVATE, }; enum { JS_ATOM_HASH_SYMBOL, JS_ATOM_HASH_PRIVATE, }; typedef enum { JS_ATOM_KIND_STRING, JS_ATOM_KIND_SYMBOL, JS_ATOM_KIND_PRIVATE, } 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 = 0, atom_type = 3, for JS_ATOM_TYPE_PRIVATE: hash = 1, 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; }; 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 : 3; /* see JSVarKindEnum */ /* 9 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; 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_PRIVATE_FIELD, JS_VAR_PRIVATE_METHOD, JS_VAR_PRIVATE_GETTER, JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ } JSVarKindEnum; typedef struct JSVarDef { JSAtom var_name; int scope_level; /* index into fd->scopes of this variable lexical scope */ int scope_next; /* index into fd->vars of the next variable in the * same or enclosing lexical scope */ uint8_t is_func_var : 1; /* used for the function self reference */ 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_or_scope_idx : 24; /* only used during compilation */ } 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 enum JSFunctionKindEnum { JS_FUNC_NORMAL = 0, JS_FUNC_GENERATOR = (1 << 0), JS_FUNC_ASYNC = (1 << 1), JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), } JSFunctionKindEnum; 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; /* true if home_object needs to be initialized */ uint8_t need_home_object : 1; uint8_t func_kind : 2; uint8_t new_target_allowed : 1; uint8_t super_call_allowed : 1; uint8_t super_allowed : 1; uint8_t arguments_allowed : 1; uint8_t has_debug : 1; uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ uint8_t read_only_bytecode : 1; /* XXX: 4 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 */ 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 line_num; int source_len; int pc2line_len; uint8_t *pc2line_buf; char *source; } debug; } JSFunctionBytecode; typedef struct JSBoundFunction { JSValue func_obj; JSValue this_val; int argc; JSValue argv[0]; } JSBoundFunction; typedef enum JSIteratorKindEnum { JS_ITERATOR_KIND_KEY, JS_ITERATOR_KIND_VALUE, JS_ITERATOR_KIND_KEY_AND_VALUE, } JSIteratorKindEnum; typedef struct JSForInIterator { JSValue obj; BOOL is_array; uint32_t array_length; uint32_t idx; } JSForInIterator; typedef struct JSRegExp { JSString *pattern; JSString *bytecode; /* also contains the flags */ } JSRegExp; typedef struct JSProxyData { JSValue target; JSValue handler; JSValue proto; uint8_t is_func; uint8_t is_revoked; } JSProxyData; typedef struct JSArrayBuffer { int byte_length; /* 0 if detached */ uint8_t detached; uint8_t shared; /* if shared, the array buffer cannot be detached */ uint8_t *data; /* NULL if detached */ struct list_head array_list; void *opaque; JSFreeArrayBufferDataFunc *free_func; } JSArrayBuffer; typedef struct JSTypedArray { struct list_head link; /* link to arraybuffer */ JSObject *obj; /* back pointer to the TypedArray/DataView object */ JSObject *buffer; /* based array buffer */ uint32_t offset; /* offset in the array buffer */ uint32_t length; /* length in the array buffer */ } JSTypedArray; typedef struct JSAsyncFunctionState { JSValue this_val; /* 'this' generator argument */ int argc; /* number of function arguments */ BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */ JSStackFrame frame; } JSAsyncFunctionState; /* XXX: could use an object instead to avoid the JS_TAG_ASYNC_FUNCTION tag for the GC */ typedef struct JSAsyncFunctionData { JSGCObjectHeader header; /* must come first */ JSValue resolving_funcs[2]; BOOL is_active; /* true if the async function state is valid */ JSAsyncFunctionState func_state; } JSAsyncFunctionData; 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 JSReqModuleEntry { JSAtom module_name; JSModuleDef *module; /* used using resolution */ } JSReqModuleEntry; typedef enum JSExportTypeEnum { JS_EXPORT_TYPE_LOCAL, JS_EXPORT_TYPE_INDIRECT, } JSExportTypeEnum; typedef struct JSExportEntry { union { struct { int var_idx; /* closure variable index */ JSVarRef *var_ref; /* if != NULL, reference to the variable */ } local; /* for local export */ int req_module_idx; /* module for indirect export */ } u; JSExportTypeEnum export_type; JSAtom local_name; /* '*' if export ns from. not used for local export after compilation */ JSAtom export_name; /* exported variable name */ } JSExportEntry; typedef struct JSStarExportEntry { int req_module_idx; /* in req_module_entries */ } JSStarExportEntry; typedef struct JSImportEntry { int var_idx; /* closure variable index */ JSAtom import_name; int req_module_idx; /* in req_module_entries */ } JSImportEntry; struct JSModuleDef { JSRefCountHeader header; /* must come first, 32-bit */ JSAtom module_name; struct list_head link; JSReqModuleEntry *req_module_entries; int req_module_entries_count; int req_module_entries_size; JSExportEntry *export_entries; int export_entries_count; int export_entries_size; JSStarExportEntry *star_export_entries; int star_export_entries_count; int star_export_entries_size; JSImportEntry *import_entries; int import_entries_count; int import_entries_size; JSValue module_ns; JSValue func_obj; /* only used for JS modules */ JSModuleInitFunc *init_func; /* only used for C modules */ BOOL resolved : 8; BOOL instantiated : 8; BOOL evaluated : 8; BOOL eval_mark : 8; /* temporary use during js_evaluate_module() */ /* true if evaluation yielded an exception. It is saved in eval_exception */ BOOL eval_has_exception : 8; JSValue eval_exception; JSValue meta_obj; /* for import.meta */ }; typedef struct JSJobEntry { struct list_head link; JSContext *ctx; JSJobFunc *job_func; int argc; JSValue argv[0]; } JSJobEntry; 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 */ int (*init_func)(JSContext *ctx, JSObject *obj, JSAtom prop, void *opaque); 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 { uint32_t prop_hash_end[0]; /* hash table of size hash_mask + 1 before the start of the structure. */ 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; 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 */ uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ uint8_t is_class : 1; /* TRUE if object is a class constructor */ uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ uint16_t class_id; /* see JS_CLASS_x */ }; }; /* byte offsets: 16/24 */ JSShape *shape; /* prototype and property names + flag */ JSProperty *prop; /* array of properties */ /* byte offsets: 24/40 */ struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */ /* byte offsets: 28/48 */ union { void *opaque; struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ #ifdef CONFIG_BIGNUM struct JSFloatEnv *float_env; /* JS_CLASS_FLOAT_ENV */ struct JSOperatorSetData *operator_set; /* JS_CLASS_OPERATOR_SET */ #endif struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ struct JSFunctionBytecode *function_bytecode; JSVarRef **var_refs; JSObject *home_object; /* for 'super' access */ } func; struct { /* JS_CLASS_C_FUNCTION: 8/12 bytes */ 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_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ union { uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ } u1; union { JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ double *double_ptr; /* JS_CLASS_FLOAT64_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; /* byte sizes: 40/48/72 */ }; enum { 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_CallConstructorInternal(JSContext *ctx, JSValueConst func_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); 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_DumpValueShort(JSRuntime *rt, JSValueConst val); static __maybe_unused void JS_DumpValue(JSContext *ctx, JSValueConst val); static __maybe_unused void JS_PrintValue(JSContext *ctx, const char *str, JSValueConst val); static __maybe_unused void JS_DumpShapes(JSRuntime *rt); 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_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_for_in_iterator_finalizer(JSRuntime *rt, JSValue val); static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_regexp_finalizer(JSRuntime *rt, JSValue val); static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val); static void js_typed_array_finalizer(JSRuntime *rt, JSValue val); static void js_typed_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_proxy_finalizer(JSRuntime *rt, JSValue val); static void js_proxy_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_map_finalizer(JSRuntime *rt, JSValue val); static void js_map_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val); static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val); static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val); static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_generator_finalizer(JSRuntime *rt, JSValue obj); static void js_generator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_promise_finalizer(JSRuntime *rt, JSValue val); static void js_promise_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val); static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); #ifdef CONFIG_BIGNUM static void js_operator_set_finalizer(JSRuntime *rt, JSValue val); static void js_operator_set_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); #endif 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 int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); 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, JSValue op1, JSValue 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 JSProperty *add_property(JSContext *ctx, JSObject *p, JSAtom prop, int prop_flags); #ifdef CONFIG_BIGNUM static void js_float_env_finalizer(JSRuntime *rt, JSValue val); static JSValue JS_NewBigFloat(JSContext *ctx); static inline bf_t *JS_GetBigFloat(JSValueConst val) { JSBigFloat *p = JS_VALUE_GET_PTR(val); return &p->num; } static JSValue JS_NewBigDecimal(JSContext *ctx); static inline bfdec_t *JS_GetBigDecimal(JSValueConst val) { JSBigDecimal *p = JS_VALUE_GET_PTR(val); return &p->num; } static JSValue JS_NewBigInt(JSContext *ctx); static inline bf_t *JS_GetBigInt(JSValueConst val) { JSBigFloat *p = JS_VALUE_GET_PTR(val); return &p->num; } static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val, BOOL convert_to_safe_integer); static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val); static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val); static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf); static bf_t *JS_ToBigFloat(JSContext *ctx, bf_t *buf, JSValueConst val); static JSValue JS_ToBigDecimalFree(JSContext *ctx, JSValue val, BOOL allow_null_or_undefined); static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val); #endif JSValue JS_ThrowOutOfMemory(JSContext *ctx); static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); static JSValueConst js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, JSValueConst proto_val, BOOL throw_flag); static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj); static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj); static int js_proxy_isArray(JSContext *ctx, JSValueConst obj); 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, const JSString *p2, int len); static void reset_weak_ref(JSRuntime *rt, JSObject *p); static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p); static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p); static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, BOOL is_arg); static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags); static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val); static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); 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 js_free_module_def(JSContext *ctx, JSModuleDef *m); static JSValue js_import_meta(JSContext *ctx); static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier); static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); static JSValue js_new_promise_capability(JSContext *ctx, JSValue *resolving_funcs, JSValueConst ctor); static __exception int perform_promise_then(JSContext *ctx, JSValueConst promise, JSValueConst *resolve_reject, JSValueConst *cap_resolving_funcs); static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); 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 int JS_NumberIsInteger(JSContext *ctx, JSValueConst val); static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val); 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 async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, JS_MarkFunc *mark_func); 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 JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, JSValueConst sync_iter); 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 void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s); static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; static const JSClassExoticMethods js_proxy_exotic_methods; static const JSClassExoticMethods js_module_ns_exotic_methods; static 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); } #ifdef CONFIG_BIGNUM /* called by libbf */ static void *js_bf_realloc(void *opaque, void *ptr, size_t size) { JSRuntime *rt = opaque; return js_realloc_rt(rt, ptr, size); } #endif /* CONFIG_BIGNUM */ /* 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 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'; } 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_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ { JS_ATOM_Arguments, NULL, NULL }, /* JS_CLASS_MAPPED_ARGUMENTS */ { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ { JS_ATOM_Function, NULL, NULL }, /* 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_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ #ifdef CONFIG_BIGNUM { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ #endif { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ #ifdef CONFIG_BIGNUM { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ { JS_ATOM_BigFloat, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_FLOAT */ { JS_ATOM_BigFloatEnv, js_float_env_finalizer, NULL }, /* JS_CLASS_FLOAT_ENV */ { JS_ATOM_BigDecimal, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_DECIMAL */ { JS_ATOM_OperatorSet, js_operator_set_finalizer, js_operator_set_mark }, /* JS_CLASS_OPERATOR_SET */ #endif { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ { JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */ { JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */ { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ }; 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; } #ifdef CONFIG_BIGNUM static JSValue JS_ThrowUnsupportedOperation(JSContext *ctx) { return JS_ThrowTypeError(ctx, "unsupported operation"); } static JSValue invalid_to_string(JSContext *ctx, JSValueConst val) { return JS_ThrowUnsupportedOperation(ctx); } static JSValue invalid_from_string(JSContext *ctx, const char *buf, int radix, int flags, slimb_t *pexponent) { return JS_NAN; } static int invalid_unary_arith(JSContext *ctx, JSValue *pres, OPCodeEnum op, JSValue op1) { JS_FreeValue(ctx, op1); JS_ThrowUnsupportedOperation(ctx); return -1; } static int invalid_binary_arith(JSContext *ctx, OPCodeEnum op, JSValue *pres, JSValue op1, JSValue op2) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); JS_ThrowUnsupportedOperation(ctx); return -1; } static JSValue invalid_mul_pow10_to_float64(JSContext *ctx, const bf_t *a, int64_t exponent) { return JS_ThrowUnsupportedOperation(ctx); } static int invalid_mul_pow10(JSContext *ctx, JSValue *sp) { JS_ThrowUnsupportedOperation(ctx); return -1; } static void set_dummy_numeric_ops(JSNumericOperations *ops) { ops->to_string = invalid_to_string; ops->from_string = invalid_from_string; ops->unary_arith = invalid_unary_arith; ops->binary_arith = invalid_binary_arith; ops->mul_pow10_to_float64 = invalid_mul_pow10_to_float64; ops->mul_pow10 = invalid_mul_pow10; } #endif /* CONFIG_BIGNUM */ 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; #ifdef CONFIG_BIGNUM bf_context_init(&rt->bf_ctx, js_bf_realloc, rt); set_dummy_numeric_ops(&rt->bigint_ops); set_dummy_numeric_ops(&rt->bigfloat_ops); set_dummy_numeric_ops(&rt->bigdecimal_ops); #endif 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 init_list_head(&rt->job_list); 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_ARGUMENTS].exotic = &js_arguments_exotic_methods; rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_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; rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call; if (init_shape_hash(rt)) goto fail; return rt; fail: JS_FreeRuntime(rt); return NULL; } /* default memory allocation functions with memory limitation */ static inline size_t js_def_malloc_usable_size(void *ptr) { #if defined(__APPLE__) return malloc_size(ptr); #elif defined(_WIN32) return _msize(ptr); #elif defined(EMSCRIPTEN) return 0; #elif defined(__linux__) return malloc_usable_size(ptr); #else /* change this to `return 0;` if compilation fails */ return malloc_usable_size(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, #if defined(__APPLE__) malloc_size, #elif defined(_WIN32) (size_t (*)(const void *))_msize, #elif defined(EMSCRIPTEN) NULL, #elif defined(__linux__) (size_t (*)(const void *))malloc_usable_size, #else /* change this to `NULL,` if compilation fails */ malloc_usable_size, #endif }; 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; } #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_SetCanBlock(JSRuntime *rt, BOOL can_block) { rt->can_block = can_block; } /* return 0 if OK, < 0 if exception */ int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, int argc, JSValueConst *argv) { JSRuntime *rt = ctx->rt; JSJobEntry *e; int i; e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); if (!e) return -1; e->ctx = ctx; e->job_func = job_func; e->argc = argc; for(i = 0; i < argc; i++) { e->argv[i] = JS_DupValue(ctx, argv[i]); } list_add_tail(&e->link, &rt->job_list); return 0; } BOOL JS_IsJobPending(JSRuntime *rt) { return !list_empty(&rt->job_list); } /* return < 0 if exception, 0 if no job pending, 1 if a job was executed successfully. the context of the job is stored in '*pctx' */ int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) { JSContext *ctx; JSJobEntry *e; JSValue res; int i, ret; if (list_empty(&rt->job_list)) { *pctx = NULL; return 0; } /* get the first pending job and execute it */ e = list_entry(rt->job_list.next, JSJobEntry, link); list_del(&e->link); ctx = e->ctx; res = e->job_func(e->ctx, e->argc, (JSValueConst *)e->argv); for(i = 0; i < e->argc; i++) JS_FreeValue(ctx, e->argv[i]); if (JS_IsException(res)) ret = -1; else ret = 1; JS_FreeValue(ctx, res); js_free(ctx, e); *pctx = ctx; return ret; } 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; list_for_each_safe(el, el1, &rt->context_list) { JSContext *ctx = list_entry(el, JSContext, link); JS_FreeContext(ctx); } list_for_each_safe(el, el1, &rt->job_list) { JSJobEntry *e = list_entry(el, JSJobEntry, link); for(i = 0; i < e->argc; i++) JS_FreeValueRT(rt, e->argv[i]); js_free_rt(rt, e); } init_list_head(&rt->job_list); JS_RunGC(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 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 CONFIG_BIGNUM bf_context_end(&rt->bf_ctx); #endif #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_GLOBAL_SYMBOL: printf("Symbol.for("); JS_DumpString(rt, p); printf(")"); break; case JS_ATOM_TYPE_SYMBOL: if (p->hash == JS_ATOM_HASH_SYMBOL) { printf("Symbol("); JS_DumpString(rt, p); printf(")"); } else { printf("Private("); 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 { JSMallocState ms = rt->malloc_state; rt->mf.js_free(&ms, rt); } } #if defined(EMSCRIPTEN) /* currently no stack limitation */ static inline uint8_t *js_get_stack_pointer(void) { return NULL; } static inline BOOL js_check_stack_overflow(JSContext *ctx, size_t alloca_size) { return FALSE; } #else /* Note: OS and CPU dependent */ static inline uint8_t *js_get_stack_pointer(void) { return __builtin_frame_address(0); } static inline BOOL js_check_stack_overflow(JSContext *ctx, size_t alloca_size) { size_t size; size = ctx->stack_top - js_get_stack_pointer(); return unlikely((size + alloca_size) > ctx->stack_size); } #endif JSContext *JS_NewContextRaw(JSRuntime *rt) { JSContext *ctx; int i; ctx = js_mallocz_rt(rt, sizeof(JSContext)); if (!ctx) return NULL; 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); ctx->stack_top = js_get_stack_pointer(); ctx->stack_size = JS_DEFAULT_STACK_SIZE; ctx->current_exception = JS_NULL; #ifdef CONFIG_BIGNUM ctx->bf_ctx = &rt->bf_ctx; ctx->fp_env.prec = 113; ctx->fp_env.flags = bf_set_exp_bits(15) | BF_RNDN | BF_FLAG_SUBNORMAL; #endif for(i = 0; i < rt->class_count; i++) ctx->class_proto[i] = JS_NULL; ctx->regexp_ctor = JS_NULL; ctx->promise_ctor = JS_NULL; init_list_head(&ctx->loaded_modules); JS_AddIntrinsicBasicObjects(ctx); return ctx; } JSContext *JS_NewContext(JSRuntime *rt) { JSContext *ctx; ctx = JS_NewContextRaw(rt); if (!ctx) return NULL; JS_AddIntrinsicBaseObjects(ctx); JS_AddIntrinsicDate(ctx); JS_AddIntrinsicEval(ctx); JS_AddIntrinsicStringNormalize(ctx); JS_AddIntrinsicRegExp(ctx); JS_AddIntrinsicJSON(ctx); JS_AddIntrinsicProxy(ctx); JS_AddIntrinsicMapSet(ctx); JS_AddIntrinsicTypedArrays(ctx); JS_AddIntrinsicPromise(ctx); #ifdef CONFIG_BIGNUM JS_AddIntrinsicBigInt(ctx); #endif 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]); } typedef enum JSFreeModuleEnum { JS_FREE_MODULE_ALL, JS_FREE_MODULE_NOT_RESOLVED, JS_FREE_MODULE_NOT_EVALUATED, } JSFreeModuleEnum; /* XXX: would be more efficient with separate module lists */ static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) { struct list_head *el, *el1; list_for_each_safe(el, el1, &ctx->loaded_modules) { JSModuleDef *m = list_entry(el, JSModuleDef, link); if (flag == JS_FREE_MODULE_ALL || (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved) || (flag == JS_FREE_MODULE_NOT_EVALUATED && !m->evaluated)) { js_free_module_def(ctx, m); } } } void JS_FreeContext(JSContext *ctx) { JSRuntime *rt = ctx->rt; int i; #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_free_modules(ctx, JS_FREE_MODULE_ALL); JS_FreeValue(ctx, ctx->current_exception); 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->iterator_proto); JS_FreeValue(ctx, ctx->async_iterator_proto); JS_FreeValue(ctx, ctx->promise_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); list_del(&ctx->link); js_free_rt(ctx->rt, ctx); } JSRuntime *JS_GetRuntime(JSContext *ctx) { return ctx->rt; } void JS_SetMaxStackSize(JSContext *ctx, size_t stack_size) { ctx->stack_size = stack_size; } static inline BOOL is_strict_mode(JSContext *ctx) { JSStackFrame *sf = ctx->current_stack_frame; return (sf && (sf->js_mode & JS_MODE_STRICT)); } #ifdef CONFIG_BIGNUM static inline BOOL is_math_mode(JSContext *ctx) { JSStackFrame *sf = ctx->current_stack_frame; return (sf && (sf->js_mode & JS_MODE_MATH)); } #endif JSValue JS_NewInt64(JSContext *ctx, int64_t v) { if (v == (int32_t)v) { return JS_NewInt32(ctx, v); } else { return __JS_NewFloat64(ctx, (double)v); } } static force_inline JSValue JS_NewUint32(JSContext *ctx, uint32_t val) { JSValue v; if (val <= 0x7fffffff) { v = JS_MKVAL(JS_TAG_INT, val); } else { v = __JS_NewFloat64(ctx, val); } return v; } /* 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; if (p->is_wide_char) c = p->u.str16[0]; else c = p->u.str8[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++) { if (p->is_wide_char) c = p->u.str16[i]; else c = p->u.str8[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 __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) { int i, c, 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++) { if (p->is_wide_char) c = p->u.str16[i]; else c = p->u.str8[i]; if (c == sep || c == '\\') { putchar('\\'); putchar(c); } else if (c >= ' ' && c <= 126) { putchar(c); } else if (c == '\n') { putchar('\\'); putchar('n'); } else { printf("\\u%04x", c); } } 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_Private_brand) atom_type = JS_ATOM_TYPE_PRIVATE; else 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_GLOBAL_SYMBOL: return JS_ATOM_KIND_SYMBOL; case JS_ATOM_TYPE_SYMBOL: switch(p->hash) { case JS_ATOM_HASH_SYMBOL: return JS_ATOM_KIND_SYMBOL; case JS_ATOM_HASH_PRIVATE: return JS_ATOM_KIND_PRIVATE; default: abort(); } 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 0 printf("__JS_NewAtom: "); JS_DumpString(rt, str); printf("\n"); #endif 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, str, len) == 0) { if (!__JS_AtomIsConst(i)) p->header.ref_count++; goto done; } i = p->hash_next; } } else { h1 = 0; /* avoid warning */ if (atom_type == JS_ATOM_TYPE_SYMBOL) { h = JS_ATOM_HASH_SYMBOL; } else { h = JS_ATOM_HASH_PRIVATE; atom_type = JS_ATOM_TYPE_SYMBOL; } } 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 { p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ if (!p) return JS_ATOM_NULL; p->header.ref_count = 1; p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ p->len = 0; #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); } 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) { #if 0 /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */ if (unlikely(i == JS_ATOM_NULL)) { p->header.ref_count = INT32_MAX / 2; return; } #endif 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 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); } /* 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); } JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) { JSValue val; if (len == 0 || !is_digit(*str)) { 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; snprintf(buf, sizeof(buf), "%u", n); val = JS_NewString(ctx, buf); 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; snprintf(buf, sizeof(buf), "%" PRId64 , n); val = JS_NewString(ctx, buf); 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]); } /* descr must be a non-numeric string atom */ static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, int atom_type) { JSRuntime *rt = ctx->rt; JSString *p; assert(!__JS_AtomIsTaggedInt(descr)); assert(descr < rt->atom_size); p = rt->atom_array[descr]; JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); return JS_NewSymbol(ctx, p, atom_type); } #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++) { if (str->is_wide_char) c = str->u.str16[i]; else c = str->u.str8[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)) { snprintf(buf, sizeof(buf), "%u", __JS_AtomToUInt32(atom)); return JS_NewString(ctx, buf); } 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_UNDEFINED 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, len, 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_UNDEFINED; p = p1; len = p->len; if (p->is_wide_char) { const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len; if (r >= r_end) return JS_UNDEFINED; c = *r; if (c == '-') { if (r >= r_end) return JS_UNDEFINED; r++; c = *r; /* -0 case is specific */ if (c == '0' && len == 2) goto minus_zero; } /* XXX: should test NaN, but the tests do not check it */ if (!is_num(c)) { /* XXX: String should be normalized, therefore 8-bit only */ const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; if (!(c =='I' && (r_end - r) == 8 && !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) return JS_UNDEFINED; } } else { const uint8_t *r = p->u.str8, *r_end = p->u.str8 + len; if (r >= r_end) return JS_UNDEFINED; c = *r; if (c == '-') { if (r >= r_end) return JS_UNDEFINED; r++; c = *r; /* -0 case is specific */ if (c == '0' && len == 2) { minus_zero: return __JS_NewFloat64(ctx, -0.0); } } if (!is_num(c)) { if (!(c =='I' && (r_end - r) == 8 && !memcmp(r + 1, "nfinity", 7))) return JS_UNDEFINED; } } /* XXX: bignum: would be better to only accept integer to avoid relying on current floating point precision */ /* 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)); JS_FreeValue(ctx, str); if (ret == 0) { return num; } else { JS_FreeValue(ctx, num); return JS_UNDEFINED; } } /* return -1 if exception or TRUE/FALSE */ static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) { JSValue num; num = JS_AtomIsNumericIndex1(ctx, atom); if (likely(JS_IsUndefined(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->hash == JS_ATOM_HASH_SYMBOL) || p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && !(p->len == 0 && p->is_wide_char != 0)); } static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) { char buf[ATOM_GET_STR_BUF_SIZE]; const char *p; int i; /* XXX: should handle embedded null characters */ /* XXX: should move encoding code to JS_AtomGetStr */ p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); for (i = 0; p[i]; i++) { int c = (unsigned char)p[i]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) break; } if (i > 0 && p[i] == '\0') { printf("%s", p); } else { putchar('"'); printf("%.*s", i, p); for (; p[i]; i++) { int c = (unsigned char)p[i]; if (c == '\"' || c == '\\') { putchar('\\'); putchar(c); } else if (c >= ' ' && c <= 126) { putchar(c); } else if (c == '\n') { putchar('\\'); putchar('n'); } else { printf("\\u%04x", c); } } putchar('\"'); } } /* free with JS_FreeCString() */ const char *JS_AtomToCString(JSContext *ctx, JSAtom atom) { JSValue str; const char *cstr; str = JS_AtomToString(ctx, atom); if (JS_IsException(str)) return NULL; cstr = JS_ToCString(ctx, str); JS_FreeValue(ctx, str); return cstr; } /* return a string atom containing name concatenated with str1 */ static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) { JSValue str; JSAtom atom; const char *cstr; char *cstr2; size_t len, len1; str = JS_AtomToString(ctx, name); if (JS_IsException(str)) return JS_ATOM_NULL; cstr = JS_ToCStringLen(ctx, &len, str); if (!cstr) goto fail; len1 = strlen(str1); cstr2 = js_malloc(ctx, len + len1 + 1); if (!cstr2) goto fail; memcpy(cstr2, cstr, len); memcpy(cstr2 + len, str1, len1); cstr2[len + len1] = '\0'; atom = JS_NewAtomLen(ctx, cstr2, len + len1); js_free(ctx, cstr2); JS_FreeCString(ctx, cstr); JS_FreeValue(ctx, str); return atom; fail: JS_FreeCString(ctx, cstr); JS_FreeValue(ctx, str); return JS_ATOM_NULL; } static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) { char buf[16]; snprintf(buf, sizeof(buf), "%u", n); return js_atom_concat_str(ctx, name, buf); } 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; /* XXX: make it thread safe */ class_id = *pclass_id; if (class_id == 0) { class_id = js_class_id_alloc++; *pclass_id = class_id; } return 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 < 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(JSContext *ctx, const uint8_t *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_string16(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(ctx, &ch8, 1); } else { uint16_t ch16 = c; return js_new_string16(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(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(ctx, 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 */ c -= 0x10000; if (string_buffer_putc16(s, (c >> 10) + 0xd800)) return -1; c = (c & 0x3ff) + 0xdc00; } return string_buffer_putc16(s, c); } static int string_get(const JSString *p, int idx) { return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx]; } 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 (c >= 0xd800 && c < 0xdc00 && idx < p->len) { c1 = p->u.str16[idx]; if (c1 >= 0xdc00 && c1 < 0xe000) { c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000; 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)) { 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; p = p_start; while (p < p_end && *p < 128) p++; len1 = p - p_start; if (len1 > JS_STRING_LEN_MAX) return JS_ThrowInternalError(ctx, "string too long"); if (p == p_end) { /* ASCII string */ return js_new_string8(ctx, (const uint8_t *)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 */ c -= 0x10000; string_buffer_putc16(b, (c >> 10) + 0xd800); c = (c & 0x3ff) + 0xdc00; } 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_NewString(JSContext *ctx, const char *str) { return JS_NewStringLen(ctx, str, strlen(str)); } 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 (c >= 0xd800 && c < 0xdc00) { if (pos < len && !cesu8) { c1 = src[pos]; if (c1 >= 0xdc00 && c1 < 0xe000) { pos++; /* surrogate pair */ c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000; } 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 = (JSString *)(void *)(ptr - offsetof(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, const JSString *p2, int len) { int res; if (likely(!p1->is_wide_char)) { if (likely(!p2->is_wide_char)) res = memcmp(p1->u.str8, p2->u.str8, len); else res = -memcmp16_8(p2->u.str16, p1->u.str8, len); } else { if (!p2->is_wide_char) res = memcmp16_8(p1->u.str16, p2->u.str8, len); else res = memcmp16(p1->u.str16, p2->u.str16, 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, p2, 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); } /* op1 and op2 are converted to strings. For convience, op1 or op2 = JS_EXCEPTION are accepted and return JS_EXCEPTION. */ static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) { JSValue ret; JSString *p1, *p2; if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) { 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)) { op2 = JS_ToStringFree(ctx, op2); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); return JS_EXCEPTION; } } p1 = JS_VALUE_GET_STRING(op1); p2 = JS_VALUE_GET_STRING(op2); /* XXX: could also check if p1 is empty */ if (p2->len == 0) { goto ret_op1; } if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char && js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) { /* Concatenate in place in available space at the end of p1 */ if (p1->is_wide_char) { memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); p1->len += p2->len; } else { memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); p1->len += p2->len; p1->u.str8[p1->len] = '\0'; } ret_op1: JS_FreeValue(ctx, op2); return op1; } ret = JS_ConcatString1(ctx, p1, p2); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return ret; } /* 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 void *get_alloc_from_shape(JSShape *sh) { return sh->prop_hash_end - ((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(sh->prop_hash_end - hash_size, 0, sizeof(sh->prop_hash_end[0]) * hash_size); sh->prop_hash_mask = hash_size - 1; sh->prop_count = 0; sh->prop_size = prop_size; /* 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; 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; if (new_hash_size != (sh->prop_hash_mask + 1)) { JSShape *old_sh; /* 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); /* copy all the fields and the 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); new_hash_mask = new_hash_size - 1; sh->prop_hash_mask = new_hash_mask; memset(sh->prop_hash_end - new_hash_size, 0, sizeof(sh->prop_hash_end[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 = sh->prop_hash_end[-h - 1]; sh->prop_hash_end[-h - 1] = i + 1; } } js_free(ctx, get_alloc_from_shape(old_sh)); } else { /* only resize the properties */ list_del(&sh->header.link); sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh), get_shape_size(new_hash_size, new_size)); if (unlikely(!sh_alloc)) { /* insert again in the GC list */ list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); return -1; } sh = get_shape_from_alloc(sh_alloc, new_hash_size); list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); } *psh = sh; sh->prop_size = new_size; 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 = sh->prop_hash_end[-h - 1]; sh->prop_hash_end[-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->is_uncatchable_error = 0; p->is_class = 0; p->tmp_mark = 0; p->first_weak_ref = 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_UNDEFINED; break; case JS_CLASS_ARGUMENTS: case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_FLOAT64_ARRAY: p->is_exotic = 1; p->fast_array = 1; p->u.array.u.ptr = NULL; p->u.array.count = 0; break; case JS_CLASS_DATAVIEW: p->u.array.u.ptr = NULL; p->u.array.count = 0; break; case JS_CLASS_NUMBER: case JS_CLASS_STRING: case JS_CLASS_BOOLEAN: case JS_CLASS_SYMBOL: case JS_CLASS_DATE: #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: case JS_CLASS_BIG_FLOAT: case JS_CLASS_BIG_DECIMAL: #endif p->u.object_data = JS_UNDEFINED; 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); } #if 0 static JSValue JS_GetObjectData(JSContext *ctx, JSValueConst obj) { 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: case JS_CLASS_DATE: #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: case JS_CLASS_BIG_FLOAT: case JS_CLASS_BIG_DECIMAL: #endif return JS_DupValue(ctx, p->u.object_data); } } return JS_UNDEFINED; } #endif 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: case JS_CLASS_DATE: #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: case JS_CLASS_BIG_FLOAT: case JS_CLASS_BIG_DECIMAL: #endif JS_FreeValue(ctx, p->u.object_data); p->u.object_data = val; 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 || class_id == JS_CLASS_GENERATOR_FUNCTION || class_id == JS_CLASS_ASYNC_FUNCTION || class_id == JS_CLASS_ASYNC_GENERATOR_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 void js_method_set_home_object(JSContext *ctx, JSValueConst func_obj, JSValueConst home_obj) { JSObject *p, *p1; JSFunctionBytecode *b; if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) return; p = JS_VALUE_GET_OBJ(func_obj); if (!js_class_has_bytecode(p->class_id)) return; b = p->u.func.function_bytecode; if (b->need_home_object) { p1 = p->u.func.home_object; if (p1) { JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); } if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) p1 = JS_VALUE_GET_OBJ(JS_DupValue(ctx, home_obj)); else p1 = NULL; p->u.func.home_object = p1; } } 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; js_method_set_home_object(ctx, func_obj, home_obj); 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.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); 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_UNDEFINED; } 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 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) { /* nothing to do */ } } 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 = sh->prop_hash_end[-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 = sh->prop_hash_end[-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); remove_gc_object(&var_ref->header); } else { list_del(&var_ref->header.link); /* still on the stack */ } 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_UNDEFINED; } 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_bytecode_function_finalizer(JSRuntime *rt, JSValue val) { JSObject *p1, *p = JS_VALUE_GET_OBJ(val); JSFunctionBytecode *b; JSVarRef **var_refs; int i; p1 = p->u.func.home_object; if (p1) { JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); } 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 (p->u.func.home_object) { JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), mark_func); } if (b) { if (var_refs) { for(i = 0; i < b->closure_var_count; i++) { JSVarRef *var_ref = var_refs[i]; if (var_ref && var_ref->is_detached) { 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 js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSForInIterator *it = p->u.for_in_iterator; JS_FreeValueRT(rt, it->obj); js_free_rt(rt, it); } static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSForInIterator *it = p->u.for_in_iterator; JS_MarkValue(rt, it->obj, 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; if (unlikely(p->first_weak_ref)) { reset_weak_ref(rt, p); } 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; p->u.func.home_object = NULL; remove_gc_object(&p->header); if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, p); } } 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_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); if (rt->gc_phase == JS_GC_PHASE_NONE) { free_zero_refcount(rt); } } } break; case JS_TAG_MODULE: abort(); /* never freed here */ break; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: { JSBigFloat *bf = JS_VALUE_GET_PTR(v); bf_delete(&bf->num); js_free_rt(rt, bf); } break; case JS_TAG_BIG_DECIMAL: { JSBigDecimal *bf = JS_VALUE_GET_PTR(v); bfdec_delete(&bf->num); js_free_rt(rt, bf); } break; #endif case JS_TAG_SYMBOL: { JSAtomStruct *p = JS_VALUE_GET_PTR(v); JS_FreeAtomStruct(rt, p); } break; default: printf("__JS_FreeValue: unknown tag=%d\n", tag); 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) { if (pr->u.var_ref->is_detached) { /* 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) { /* nothing to do */ } } 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); } } break; case JS_GC_OBJ_TYPE_VAR_REF: { JSVarRef *var_ref = (JSVarRef *)gp; /* only detached variable referenced are taken into account */ assert(var_ref->is_detached); JS_MarkValue(rt, *var_ref->pvalue, mark_func); } break; case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: { JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp; if (s->is_active) async_func_mark(rt, &s->func_state, mark_func); JS_MarkValue(rt, s->resolving_funcs[0], mark_func); JS_MarkValue(rt, s->resolving_funcs[1], mark_func); } break; case JS_GC_OBJ_TYPE_SHAPE: { JSShape *sh = (JSShape *)gp; if (sh->proto != NULL) { mark_func(rt, &sh->proto->header); } } break; default: abort(); } } #if 0 /* not useful until realms are supported */ static void mark_context(JSRuntime *rt, JSContext *ctx) { int i; struct list_head *el; list_for_each(el, &ctx->loaded_modules) { JSModuleDef *m = list_entry(el, JSModuleDef, link); JS_MarkValue(rt, m->module_ns); JS_MarkValue(rt, m->func_obj); } JS_MarkValue(rt, ctx->current_exception); for(i = 0; i < rt->class_count; i++) JS_MarkValue(rt, ctx->class_proto[i]); JS_MarkValue(rt, ctx->regexp_ctor); JS_MarkValue(rt, ctx->function_ctor); JS_MarkValue(rt, ctx->function_proto); JS_MarkValue(rt, ctx->iterator_proto); JS_MarkValue(rt, ctx->async_iterator_proto); JS_MarkValue(rt, ctx->array_proto_values); for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) JS_MarkValue(rt, ctx->native_error_proto[i]); JS_MarkValue(rt, ctx->throw_type_error); JS_MarkValue(rt, ctx->eval_obj); JS_MarkValue(rt, ctx->global_obj); JS_MarkValue(rt, ctx->global_var_obj); } #endif 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. 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); } void JS_RunGC(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); } /* 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; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: case JS_TAG_BIG_DECIMAL: /* should track JSBigFloat usage */ break; #endif } } void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) { struct list_head *el, *el1; 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(el1, &ctx->loaded_modules) { JSModuleDef *m = list_entry(el1, JSModuleDef, link); s->memory_used_count += 1; s->memory_used_size += sizeof(*m); if (m->req_module_entries) { s->memory_used_count += 1; s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); } if (m->export_entries) { s->memory_used_count += 1; s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); for (i = 0; i < m->export_entries_count; i++) { JSExportEntry *me = &m->export_entries[i]; if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { /* potential multiple count */ s->memory_used_count += 1; compute_value_size(me->u.local.var_ref->value, hp); } } } if (m->star_export_entries) { s->memory_used_count += 1; s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); } if (m->import_entries) { s->memory_used_count += 1; s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); } compute_value_size(m->module_ns, hp); compute_value_size(m->func_obj, hp); } } 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 */ case JS_CLASS_ARGUMENTS: /* 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 */ case JS_CLASS_DATE: /* u.object_data */ #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT: /* u.object_data */ case JS_CLASS_BIG_FLOAT: /* u.object_data */ case JS_CLASS_BIG_DECIMAL: /* u.object_data */ #endif 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; case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ { JSForInIterator *it = p->u.for_in_iterator; if (it) { compute_value_size(it->obj, hp); s->memory_used_count += 1; s->memory_used_size += sizeof(*it); } } break; case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ { JSArrayBuffer *abuf = p->u.array_buffer; if (abuf) { s->memory_used_count += 1; s->memory_used_size += sizeof(*abuf); if (abuf->data) { s->memory_used_count += 1; s->memory_used_size += abuf->byte_length; } } } break; case JS_CLASS_GENERATOR: /* u.generator_data */ case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ #endif case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ case JS_CLASS_DATAVIEW: /* u.typed_array */ #ifdef CONFIG_BIGNUM case JS_CLASS_FLOAT_ENV: /* u.float_env */ #endif case JS_CLASS_MAP: /* u.map_state */ case JS_CLASS_SET: /* u.map_state */ case JS_CLASS_WEAKMAP: /* u.map_state */ case JS_CLASS_WEAKSET: /* u.map_state */ case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ case JS_CLASS_PROXY: /* u.proxy_data */ case JS_CLASS_PROMISE: /* u.promise_data */ case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ /* TODO */ 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 -- " #ifdef CONFIG_BIGNUM "BigNum " #endif CONFIG_VERSION " version, %d-bit, malloc limit: %"PRId64"\n\n", (int)sizeof(void *) * 8, (int64_t)(ssize_t)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]) { 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), js_std_class_def[class_id - 1].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) { JS_FreeValue(ctx, ctx->current_exception); ctx->current_exception = obj; return JS_EXCEPTION; } /* return the pending exception (cannot be called twice). */ JSValue JS_GetException(JSContext *ctx) { JSValue val; val = ctx->current_exception; ctx->current_exception = JS_NULL; return val; } 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; } static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, uint32_t pc_value) { const uint8_t *p_end, *p; int new_line_num, line_num, pc, v, ret; unsigned int op; if (!b->has_debug || !b->debug.pc2line_buf) { /* function was stripped */ return -1; } p = b->debug.pc2line_buf; p_end = p + b->debug.pc2line_len; pc = 0; line_num = b->debug.line_num; while (p < p_end) { op = *p++; if (op == 0) { uint32_t val; 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) { fail: /* should never happen */ return b->debug.line_num; } 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; } if (pc_value < pc) return line_num; line_num = new_line_num; } return line_num; } /* 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) /* only taken into account if filename is provided */ #define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) /* 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 backtrace_flags) { JSStackFrame *sf; JSValue str; DynBuf dbuf; const char *func_name_str; const char *str1; JSObject *p; BOOL backtrace_barrier; js_dbuf_init(ctx, &dbuf); if (filename) { dbuf_printf(&dbuf, " at %s", filename); if (line_num != -1) dbuf_printf(&dbuf, ":%d", line_num); dbuf_putc(&dbuf, '\n'); str = JS_NewString(ctx, filename); JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); if (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL) goto done; } for(sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { 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); backtrace_barrier = FALSE; if (js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b; char atom_buf[ATOM_GET_STR_BUF_SIZE]; int line_num1; b = p->u.func.function_bytecode; backtrace_barrier = b->backtrace_barrier; if (b->has_debug) { line_num1 = find_line_num(ctx, b, sf->cur_pc - b->byte_code_buf - 1); dbuf_printf(&dbuf, " (%s", JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->debug.filename)); if (line_num1 != -1) dbuf_printf(&dbuf, ":%d", line_num1); dbuf_putc(&dbuf, ')'); } } else { dbuf_printf(&dbuf, " (native)"); } dbuf_putc(&dbuf, '\n'); /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ if (backtrace_barrier) break; } done: dbuf_putc(&dbuf, '\0'); 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); } ret = JS_Throw(ctx, obj); return ret; } static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) { JSStackFrame *sf; BOOL add_backtrace; /* the backtrace is added later if called from a bytecode function */ sf = ctx->current_stack_frame; add_backtrace = !ctx->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) && is_strict_mode(ctx))) { va_start(ap, fmt); JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); va_end(ap); return -1; } else { return FALSE; } } static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) { if ((flags & JS_PROP_THROW) || ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { char buf[ATOM_GET_STR_BUF_SIZE]; JS_ThrowTypeError(ctx, "%s is read-only", JS_AtomGetStr(ctx, buf, sizeof(buf), 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) { if (!ctx->in_out_of_memory) { ctx->in_out_of_memory = TRUE; JS_ThrowInternalError(ctx, "out of memory"); ctx->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_ThrowTypeErrorNotASymbol(JSContext *ctx) { return JS_ThrowTypeError(ctx, "not a symbol"); } 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_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) { JSRuntime *rt = ctx->rt; char buf[ATOM_GET_STR_BUF_SIZE]; JSAtom name; name = rt->class_array[class_id].class_name; return JS_ThrowTypeError(ctx, "%s object expected", JS_AtomGetStr(ctx, buf, sizeof(buf), name)); } /* return -1 (exception) or TRUE/FALSE */ static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, JSValueConst proto_val, BOOL throw_flag) { JSObject *proto, *p, *p1; JSShape *sh; if (throw_flag) { if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) goto not_obj; } else { if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) 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 (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return TRUE; if (unlikely(p->class_id == JS_CLASS_PROXY)) return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); sh = p->shape; if (sh->proto == proto) return TRUE; if (!p->extensible) { if (throw_flag) { JS_ThrowTypeError(ctx, "object is not extensible"); return -1; } else { return FALSE; } } if (proto) { /* check if there is a cycle */ p1 = proto; do { if (p1 == p) { if (throw_flag) { JS_ThrowTypeError(ctx, "circular prototype chain"); return -1; } else { return FALSE; } } /* 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, TRUE); } /* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ JSValueConst JS_GetPrototype(JSContext *ctx, JSValueConst val) { JSObject *p; switch(JS_VALUE_GET_NORM_TAG(val)) { #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: val = ctx->class_proto[JS_CLASS_BIG_INT]; break; case JS_TAG_BIG_FLOAT: val = ctx->class_proto[JS_CLASS_BIG_FLOAT]; break; case JS_TAG_BIG_DECIMAL: val = ctx->class_proto[JS_CLASS_BIG_DECIMAL]; break; #endif case JS_TAG_INT: case JS_TAG_FLOAT64: val = ctx->class_proto[JS_CLASS_NUMBER]; break; case JS_TAG_BOOL: val = ctx->class_proto[JS_CLASS_BOOLEAN]; break; case JS_TAG_STRING: val = ctx->class_proto[JS_CLASS_STRING]; break; case JS_TAG_SYMBOL: val = ctx->class_proto[JS_CLASS_SYMBOL]; break; case JS_TAG_OBJECT: p = JS_VALUE_GET_OBJ(val); if (unlikely(p->class_id == JS_CLASS_PROXY)) { val = js_proxy_getPrototypeOf(ctx, val); } else { p = p->shape->proto; if (!p) val = JS_NULL; else val = JS_MKPTR(JS_TAG_OBJECT, p); } break; case JS_TAG_NULL: case JS_TAG_UNDEFINED: default: val = JS_NULL; break; } return val; } /* return TRUE, FALSE or (-1) in case of exception */ static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) { JSValue obj_proto; JSObject *proto; const JSObject *p, *proto1; BOOL ret; if (!JS_IsFunction(ctx, obj)) return FALSE; p = JS_VALUE_GET_OBJ(obj); if (p->class_id == JS_CLASS_BOUND_FUNCTION) { JSBoundFunction *s = p->u.bound_function; return JS_IsInstanceOf(ctx, val, s->func_obj); } /* Only explicitly boxed values are instances of constructors */ if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return FALSE; ret = FALSE; obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { if (!JS_IsException(obj_proto)) JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); ret = -1; goto done; } proto = JS_VALUE_GET_OBJ(obj_proto); p = JS_VALUE_GET_OBJ(val); for(;;) { proto1 = p->shape->proto; if (!proto1) { if (p->class_id == JS_CLASS_PROXY) { JSValueConst proto_val; proto_val = JS_GetPrototype(ctx, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); if (JS_IsException(proto_val)) { ret = -1; goto done; } proto1 = JS_VALUE_GET_OBJ(proto_val); if (!proto1) break; } else { break; } } p = proto1; if (proto == p) { ret = TRUE; break; } } done: JS_FreeValue(ctx, obj_proto); return ret; } /* return TRUE, FALSE or (-1) in case of exception */ int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) { JSValue method; if (!JS_IsObject(obj)) goto fail; method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); if (JS_IsException(method)) return -1; if (!JS_IsNull(method) && !JS_IsUndefined(method)) { JSValue ret; ret = JS_CallFree(ctx, method, obj, 1, &val); return JS_ToBoolFree(ctx, ret); } /* legacy case */ if (!JS_IsFunction(ctx, obj)) { fail: JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); return -1; } return JS_OrdinaryIsInstanceOf(ctx, val, obj); } static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSProperty *pr) { return (*pr->u.init.init_func)(ctx, p, prop, pr->u.init.opaque); } 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: case JS_TAG_UNDEFINED: return JS_ThrowTypeError(ctx, "value has no property"); case JS_TAG_EXCEPTION: return JS_EXCEPTION; case JS_TAG_STRING: { JSString *p1 = JS_VALUE_GET_STRING(obj); if (__JS_AtomIsTaggedInt(prop)) { uint32_t idx, ch; idx = __JS_AtomToUInt32(prop); if (idx < p1->len) { if (p1->is_wide_char) ch = p1->u.str16[idx]; else ch = p1->u.str8[idx]; return js_new_string_char(ctx, ch); } } else if (prop == JS_ATOM_length) { return JS_NewInt32(ctx, p1->len); } } break; default: break; } /* cannot raise an exception */ p = JS_VALUE_GET_OBJ(JS_GetPrototype(ctx, obj)); if (!p) return JS_UNDEFINED; } 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_UNDEFINED; } 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)) 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 if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { goto typed_array_oob; } } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { int ret; ret = JS_AtomIsNumericIndex(ctx, prop); if (ret != 0) { if (ret < 0) return JS_EXCEPTION; typed_array_oob: if (typed_array_is_detached(ctx, p)) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return JS_UNDEFINED; } } } else { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em) { if (em->get_property) { /* XXX: should pass throw_ref_error */ return em->get_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop, this_obj); } if (em->get_own_property) { JSPropertyDescriptor desc; int ret; ret = em->get_own_property(ctx, &desc, JS_MKPTR(JS_TAG_OBJECT, p), prop); 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_UNDEFINED; } } static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) { char buf[ATOM_GET_STR_BUF_SIZE]; return JS_ThrowTypeError(ctx, "private class field %s does not exist", JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); } /* Private fields can be added even on non extensible objects or Proxies */ static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj, JSValueConst name, JSValue val) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; JSAtom prop; if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { JS_ThrowTypeErrorNotAnObject(ctx); goto fail; } /* safety check */ if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { JS_ThrowTypeErrorNotASymbol(ctx); goto fail; } prop = js_symbol_to_atom(ctx, (JSValue)name); p = JS_VALUE_GET_OBJ(obj); prs = find_own_property(&pr, p, prop); if (prs) { char buf[ATOM_GET_STR_BUF_SIZE]; JS_ThrowTypeError(ctx, "private class field %s already exists", JS_AtomGetStr(ctx, buf, sizeof(buf), prop)); goto fail; } pr = add_property(ctx, p, prop, JS_PROP_C_W_E); if (unlikely(!pr)) { fail: JS_FreeValue(ctx, val); return -1; } pr->u.value = val; return 0; } static JSValue JS_GetPrivateField(JSContext *ctx, JSValueConst obj, JSValueConst name) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; JSAtom prop; if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) return JS_ThrowTypeErrorNotAnObject(ctx); /* safety check */ if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) return JS_ThrowTypeErrorNotASymbol(ctx); prop = js_symbol_to_atom(ctx, (JSValue)name); p = JS_VALUE_GET_OBJ(obj); prs = find_own_property(&pr, p, prop); if (!prs) { JS_ThrowTypeErrorPrivateNotFound(ctx, prop); return JS_EXCEPTION; } return JS_DupValue(ctx, pr->u.value); } static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj, JSValueConst name, JSValue val) { JSObject *p; JSShapeProperty *prs; JSProperty *pr; JSAtom prop; if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { JS_ThrowTypeErrorNotAnObject(ctx); goto fail; } /* safety check */ if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { JS_ThrowTypeErrorNotASymbol(ctx); goto fail; } prop = js_symbol_to_atom(ctx, (JSValue)name); p = JS_VALUE_GET_OBJ(obj); prs = find_own_property(&pr, p, prop); if (!prs) { JS_ThrowTypeErrorPrivateNotFound(ctx, prop); fail: JS_FreeValue(ctx, val); return -1; } set_value(ctx, &pr->u.value, val); return 0; } static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) { JSObject *p, *p1; JSShapeProperty *prs; JSProperty *pr; JSValue brand; JSAtom brand_atom; if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { JS_ThrowTypeErrorNotAnObject(ctx); return -1; } p = JS_VALUE_GET_OBJ(home_obj); prs = find_own_property(&pr, p, JS_ATOM_Private_brand); if (!prs) { brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); if (JS_IsException(brand)) return -1; /* if the brand is not present, add it */ pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); if (!pr) { JS_FreeValue(ctx, brand); return -1; } pr->u.value = JS_DupValue(ctx, brand); } else { brand = JS_DupValue(ctx, pr->u.value); } brand_atom = js_symbol_to_atom(ctx, brand); if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { JS_ThrowTypeErrorNotAnObject(ctx); JS_FreeAtom(ctx, brand_atom); return -1; } p1 = JS_VALUE_GET_OBJ(obj); pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); JS_FreeAtom(ctx, brand_atom); if (!pr) return -1; pr->u.value = JS_UNDEFINED; return 0; } static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func) { JSObject *p, *p1, *home_obj; JSShapeProperty *prs; JSProperty *pr; JSValueConst brand; /* get the home object of 'func' */ if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) { not_obj: JS_ThrowTypeErrorNotAnObject(ctx); return -1; } p1 = JS_VALUE_GET_OBJ(func); if (!js_class_has_bytecode(p1->class_id)) goto not_obj; home_obj = p1->u.func.home_object; if (!home_obj) goto not_obj; prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); if (!prs) { JS_ThrowTypeError(ctx, "expecting private field"); return -1; } brand = pr->u.value; /* safety check */ if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) goto not_obj; /* get the brand array of 'obj' */ if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) goto not_obj; p = JS_VALUE_GET_OBJ(obj); prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand)); if (!prs) { JS_ThrowTypeError(ctx, "invalid brand on object"); return -1; } return 0; } 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; } static void js_free_prop_enum(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; 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_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) { 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) { /* the implicit GetOwnProperty raises an exception if the typed array is detached */ if ((flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) && (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) && typed_array_is_detached(ctx, p) && typed_array_get_length(ctx, p) != 0) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return -1; } num_keys_count += p->u.array.count; } else { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em && em->get_own_property_names) { if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, JS_MKPTR(JS_TAG_OBJECT, p))) return -1; for(i = 0; i < exotic_count; i++) { atom = tab_exotic[i].atom; kind = JS_AtomGetKind(ctx, atom); if (((flags >> kind) & 1) != 0) { is_enumerable = FALSE; if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { JSPropertyDescriptor desc; int res; /* set the "is_enumerable" field if necessary */ res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); if (res < 0) { js_free_prop_enum(ctx, tab_exotic, exotic_count); return -1; } if (res) { is_enumerable = ((desc.flags & JS_PROP_ENUMERABLE) != 0); js_free_desc(ctx, &desc); } tab_exotic[i].is_enumerable = is_enumerable; } if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { num_keys_count++; } else if (kind == JS_ATOM_KIND_STRING) { str_keys_count++; } else { sym_keys_count++; } } } } } } } /* fill them */ atom_count = num_keys_count + str_keys_count + sym_keys_count; /* avoid allocating 0 bytes */ tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); if (!tab_atom) { js_free_prop_enum(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) { 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) { if (p->fast_array) { for(i = 0; i < p->u.array.count; i++) { tab_atom[num_index].atom = __JS_AtomFromUInt32(i); if (tab_atom[num_index].atom == JS_ATOM_NULL) { js_free_prop_enum(ctx, tab_exotic, exotic_count); js_free_prop_enum(ctx, tab_atom, num_index); return -1; } tab_atom[num_index].is_enumerable = TRUE; num_index++; } } if (exotic_count > 0) { 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) { 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 = atom; tab_atom[j].is_enumerable = is_enumerable; } 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_UNDEFINED; desc->setter = JS_UNDEFINED; desc->value = JS_UNDEFINED; 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)) 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; if (p->class_id == JS_CLASS_ARRAY || p->class_id == JS_CLASS_ARGUMENTS) desc->flags |= JS_PROP_CONFIGURABLE; desc->getter = JS_UNDEFINED; desc->setter = JS_UNDEFINED; desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); } return TRUE; } } if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { int ret; ret = JS_AtomIsNumericIndex(ctx, prop); if (ret != 0) { if (ret < 0) return -1; if (typed_array_is_detached(ctx, p)) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return -1; } } } } 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 (Proxy 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); if (unlikely(p->class_id == JS_CLASS_PROXY)) return js_proxy_isExtensible(ctx, obj); else return p->extensible; } /* return -1 if exception (Proxy 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); if (unlikely(p->class_id == JS_CLASS_PROXY)) return js_proxy_preventExtensions(ctx, 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(;;) { if (p->is_exotic) { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em && em->has_property) return em->has_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop); } ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); if (ret != 0) return ret; if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { ret = JS_AtomIsNumericIndex(ctx, prop); if (ret != 0) { if (ret < 0) return -1; /* the detached array test was done in JS_GetOwnPropertyInternal() */ return FALSE; } } 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) { atom = 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; if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { JSObject *p; uint32_t idx, len; /* fast path for array access */ p = JS_VALUE_GET_OBJ(this_obj); idx = JS_VALUE_GET_INT(prop); len = (uint32_t)p->u.array.count; if (unlikely(idx >= len)) goto slow_path; switch(p->class_id) { case JS_CLASS_ARRAY: case JS_CLASS_ARGUMENTS: return JS_DupValue(ctx, p->u.array.u.values[idx]); case JS_CLASS_INT8_ARRAY: return JS_NewInt32(ctx, p->u.array.u.int8_ptr[idx]); case JS_CLASS_UINT8C_ARRAY: case JS_CLASS_UINT8_ARRAY: return JS_NewInt32(ctx, p->u.array.u.uint8_ptr[idx]); case JS_CLASS_INT16_ARRAY: return JS_NewInt32(ctx, p->u.array.u.int16_ptr[idx]); case JS_CLASS_UINT16_ARRAY: return JS_NewInt32(ctx, p->u.array.u.uint16_ptr[idx]); case JS_CLASS_INT32_ARRAY: return JS_NewInt32(ctx, p->u.array.u.int32_ptr[idx]); case JS_CLASS_UINT32_ARRAY: return JS_NewUint32(ctx, p->u.array.u.uint32_ptr[idx]); #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: return JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); case JS_CLASS_BIG_UINT64_ARRAY: return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); #endif case JS_CLASS_FLOAT32_ARRAY: return __JS_NewFloat64(ctx, p->u.array.u.float_ptr[idx]); case JS_CLASS_FLOAT64_ARRAY: return __JS_NewFloat64(ctx, p->u.array.u.double_ptr[idx]); default: goto slow_path; } } else { slow_path: atom = JS_ValueToAtom(ctx, prop); 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_UNDEFINED; 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); 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 = sh->prop_hash_end[-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 { sh->prop_hash_end[-h1 - 1] = pr->hash_next; } /* 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_UNDEFINED; 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 || p->class_id == JS_CLASS_ARGUMENTS) { /* 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; /* not configurable */ } } } 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); } else { JS_FreeValue(ctx, val); if ((flags & JS_PROP_THROW) || ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { JS_ThrowTypeError(ctx, "no setter for property"); return -1; } /* XXX: should return FALSE? */ } return TRUE; } /* set the array length and remove the array elements if necessary. */ static int set_array_length(JSContext *ctx, JSObject *p, JSProperty *prop, JSValue val, int flags) { uint32_t len, idx, cur_len; int i, ret; ret = JS_ToArrayLengthFree(ctx, &len, val); if (ret) return -1; 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; } prop->u.value = JS_NewUint32(ctx, len); } else { /* Note: length is always a uint32 because the object is an array */ JS_ToUint32(ctx, &cur_len, prop->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; } /* 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)) { 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) { JS_FreeValue(ctx, val); return -1; } new_size += slack / sizeof(*new_array_prop); p->u.array.u.values = new_array_prop; p->u.array.u1.size = new_size; } p->u.array.u.values[new_len - 1] = val; p->u.array.count = new_len; return TRUE; } static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) { JS_FreeValue(ctx, desc->getter); JS_FreeValue(ctx, desc->setter); JS_FreeValue(ctx, desc->value); } /* generic (and slower) version of JS_SetProperty() for Reflect.set() */ static int JS_SetPropertyGeneric(JSContext *ctx, JSObject *p, JSAtom prop, JSValue val, JSValueConst this_obj, int flags) { int ret; JSPropertyDescriptor desc; while (p != NULL) { if (p->is_exotic) { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em && em->set_property) { ret = em->set_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop, val, this_obj, flags); JS_FreeValue(ctx, val); return ret; } } ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); if (ret < 0) return ret; if (ret) { if (desc.flags & JS_PROP_GETSET) { JSObject *setter; if (JS_IsUndefined(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_error; } } break; } p = p->shape->proto; } if (!JS_IsObject(this_obj)) return JS_ThrowTypeErrorOrFalse(ctx, flags, "receiver is not an object"); p = JS_VALUE_GET_OBJ(this_obj); /* modify the property in this_obj if it already exists */ ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); if (ret < 0) 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) || p->class_id == JS_CLASS_MODULE_NS) { read_only_error: JS_FreeValue(ctx, val); return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); } } ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, JS_PROP_HAS_VALUE); JS_FreeValue(ctx, val); return ret; } ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, 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; } /* 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. */ int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValue val, int flags) { JSObject *p, *p1; JSShapeProperty *prs; JSProperty *pr; uint32_t tag; JSPropertyDescriptor desc; int ret; #if 0 printf("JS_SetPropertyInternal: "); print_atom(ctx, prop); printf("\n"); #endif tag = JS_VALUE_GET_TAG(this_obj); if (unlikely(tag != JS_TAG_OBJECT)) { switch(tag) { case JS_TAG_NULL: case JS_TAG_UNDEFINED: JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "value has no property"); return -1; default: /* even on a primitive type we can have setters on the prototype */ p = NULL; p1 = JS_VALUE_GET_OBJ(JS_GetPrototype(ctx, this_obj)); goto prototype_lookup; } } p = JS_VALUE_GET_OBJ(this_obj); retry: prs = find_own_property(&pr, p, 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 | JS_PROP_WRITABLE)) == (JS_PROP_LENGTH | JS_PROP_WRITABLE)) { assert(p->class_id == JS_CLASS_ARRAY); assert(prop == JS_ATOM_length); return set_array_length(ctx, p, pr, 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) { /* JS_PROP_WRITABLE is always true for variable references, but they are write protected in module name spaces. */ if (p->class_id == JS_CLASS_MODULE_NS) goto read_only_prop; 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)) { JS_FreeValue(ctx, val); return -1; } goto retry; } else { goto read_only_prop; } } p1 = p; 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 if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { goto typed_array_oob; } } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { ret = JS_AtomIsNumericIndex(ctx, prop); if (ret != 0) { if (ret < 0) { JS_FreeValue(ctx, val); return -1; } typed_array_oob: val = JS_ToNumberFree(ctx, val); JS_FreeValue(ctx, val); if (JS_IsException(val)) return -1; if (typed_array_is_detached(ctx, p1)) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return -1; } return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); } } } else { const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; if (em) { if (em->set_property) { ret = em->set_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p1), prop, val, this_obj, flags); JS_FreeValue(ctx, val); return ret; } if (em->get_own_property) { ret = em->get_own_property(ctx, &desc, JS_MKPTR(JS_TAG_OBJECT, p1), prop); if (ret < 0) { JS_FreeValue(ctx, val); return ret; } if (ret) { if (desc.flags & JS_PROP_GETSET) { JSObject *setter; if (JS_IsUndefined(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_UNDEFINED, JS_UNDEFINED, 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)) return -1; goto retry2; } else if (!(prs->flags & JS_PROP_WRITABLE)) { read_only_prop: JS_FreeValue(ctx, val); return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); } } } 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 (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 { generic_create_prop: ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, 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; } } 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; } /* 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) { if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { JSObject *p; uint32_t idx; double d; int32_t v; /* fast path for array access */ p = JS_VALUE_GET_OBJ(this_obj); idx = JS_VALUE_GET_INT(prop); switch(p->class_id) { case JS_CLASS_ARRAY: 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; case JS_CLASS_ARGUMENTS: if (unlikely(idx >= (uint32_t)p->u.array.count)) goto slow_path; set_value(ctx, &p->u.array.u.values[idx], val); break; case JS_CLASS_UINT8C_ARRAY: if (JS_ToUint8ClampFree(ctx, &v, val)) return -1; /* Note: the conversion can detach the typed array, so the array bound check must be done after */ if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound; p->u.array.u.uint8_ptr[idx] = v; break; case JS_CLASS_INT8_ARRAY: case JS_CLASS_UINT8_ARRAY: if (JS_ToInt32Free(ctx, &v, val)) return -1; if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound; p->u.array.u.uint8_ptr[idx] = v; break; case JS_CLASS_INT16_ARRAY: case JS_CLASS_UINT16_ARRAY: if (JS_ToInt32Free(ctx, &v, val)) return -1; if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound; p->u.array.u.uint16_ptr[idx] = v; break; case JS_CLASS_INT32_ARRAY: case JS_CLASS_UINT32_ARRAY: if (JS_ToInt32Free(ctx, &v, val)) return -1; if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound; p->u.array.u.uint32_ptr[idx] = v; break; #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: case JS_CLASS_BIG_UINT64_ARRAY: /* XXX: need specific conversion function */ { int64_t v; if (JS_ToBigInt64Free(ctx, &v, val)) return -1; if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound; p->u.array.u.uint64_ptr[idx] = v; } break; #endif case JS_CLASS_FLOAT32_ARRAY: if (JS_ToFloat64Free(ctx, &d, val)) return -1; if (unlikely(idx >= (uint32_t)p->u.array.count)) goto ta_out_of_bound; p->u.array.u.float_ptr[idx] = d; break; case JS_CLASS_FLOAT64_ARRAY: if (JS_ToFloat64Free(ctx, &d, val)) return -1; if (unlikely(idx >= (uint32_t)p->u.array.count)) { ta_out_of_bound: if (typed_array_is_detached(ctx, p)) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return -1; } else { return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); } } p->u.array.u.double_ptr[idx] = d; break; default: goto slow_path; } return TRUE; } else { JSAtom atom; int ret; slow_path: 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, 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); ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, 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 (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { ret = JS_AtomIsNumericIndex(ctx, prop); if (ret != 0) { if (ret < 0) return -1; return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); } } 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_UNDEFINED; } } 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)) { if (!(prop_flags & JS_PROP_CONFIGURABLE)) { 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 (!has_accessor && !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) { /* property already exists */ if (!check_define_prop_flags(prs->flags, flags)) { not_configurable: return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); } retry: 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 if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* clear property and update */ if (js_shape_prepare_update(ctx, p, &prs)) return -1; prs->flags &= ~JS_PROP_TMASK; pr->u.value = JS_UNDEFINED; goto retry; } 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_UNDEFINED; } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { /* Note: JS_PROP_VARREF is always writable */ } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { /* clear property and update */ if (js_shape_prepare_update(ctx, p, &prs)) return -1; prs->flags &= ~JS_PROP_TMASK; pr->u.value = JS_UNDEFINED; } else { if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && (flags & JS_PROP_HAS_VALUE) && !js_same_value(ctx, val, pr->u.value)) { goto not_configurable; } } if (prs->flags & JS_PROP_LENGTH) { if (flags & JS_PROP_HAS_VALUE) { res = set_array_length(ctx, p, pr, JS_DupValue(ctx, val), flags); } else { res = TRUE; } /* still need to reset the writable flag if needed. The JS_PROP_LENGTH is reset to have the correct read-only behavior in JS_SetProperty(). */ 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 | JS_PROP_LENGTH))) return -1; } return res; } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { if (flags & JS_PROP_HAS_VALUE) { if (p->class_id == JS_CLASS_MODULE_NS) { /* JS_PROP_WRITABLE is always true for variable references, but they are write protected in module name spaces. */ if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) goto not_configurable; } /* 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_TMASK) == JS_PROP_AUTOINIT) { /* XXX: should never happen, type was reset above */ abort(); } 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; } } } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { JSValue num; int ret; if (!__JS_AtomIsTaggedInt(prop)) { /* slow path with to handle all numeric indexes */ num = JS_AtomIsNumericIndex1(ctx, prop); if (JS_IsUndefined(num)) goto typed_array_done; if (JS_IsException(num)) return -1; ret = JS_NumberIsInteger(ctx, num); if (ret < 0) { JS_FreeValue(ctx, num); return -1; } if (!ret) { JS_FreeValue(ctx, num); return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); } ret = JS_NumberIsNegativeOrMinusZero(ctx, num); JS_FreeValue(ctx, num); if (ret) { return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); } if (!__JS_AtomIsTaggedInt(prop)) goto typed_array_oob; } idx = __JS_AtomToUInt32(prop); /* if the typed array is detached, p->u.array.count = 0 */ if (idx >= typed_array_get_length(ctx, p)) { typed_array_oob: return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); } prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE)) { return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); } if (flags & JS_PROP_HAS_VALUE) { return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), JS_DupValue(ctx, val), flags); } return TRUE; typed_array_done: ; } } return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); } static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop, int (*init_func)(JSContext *ctx, JSObject *obj, JSAtom prop, void *opaque), 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.init_func = init_func; 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_UNDEFINED, JS_UNDEFINED, 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); 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_UNDEFINED, 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) { char buf[ATOM_GET_STR_BUF_SIZE]; return JS_ThrowSyntaxError(ctx, "redeclaration of %s", JS_AtomGetStr(ctx, buf, sizeof(buf), 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; char buf[ATOM_GET_STR_BUF_SIZE]; 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_ThrowTypeError(ctx, "cannot define variable %s", JS_AtomGetStr(ctx, buf, sizeof(buf), 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_UNDEFINED; } 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_UNDEFINED, JS_UNDEFINED, 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_UNDEFINED; } } 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; } flags = JS_PROP_THROW_STRICT; if (flag != 2 && is_strict_mode(ctx)) flags |= JS_PROP_NO_ADD; return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags); } /* 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) && is_strict_mode(ctx))) { 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; case JS_CLASS_PROXY: return p->u.proxy_data->is_func; 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; } BOOL JS_IsConstructor(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->is_constructor; } BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, BOOL val) { JSObject *p; if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) return FALSE; p = JS_VALUE_GET_OBJ(func_obj); p->is_constructor = 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); } /* used to avoid catching interrupt exceptions */ BOOL JS_IsUncatchableError(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 && p->is_uncatchable_error; } void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag) { JSObject *p; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return; p = JS_VALUE_GET_OBJ(val); if (p->class_id == JS_CLASS_ERROR) p->is_uncatchable_error = flag; } void JS_ResetUncatchableError(JSContext *ctx) { JS_SetUncatchableError(ctx, ctx->current_exception, FALSE); } 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; } #define HINT_STRING 0 #define HINT_NUMBER 1 #define HINT_NONE 2 /* don't try Symbol.toPrimitive */ #define HINT_FORCE_ORDINARY (1 << 4) static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) { int i; BOOL force_ordinary; JSAtom method_name; JSValue method, ret; if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return val; force_ordinary = hint & HINT_FORCE_ORDINARY; hint &= ~HINT_FORCE_ORDINARY; if (!force_ordinary) { method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); if (JS_IsException(method)) goto exception; /* ECMA says *If exoticToPrim is not undefined* but tests in test262 use null as a non callable converter */ if (!JS_IsUndefined(method) && !JS_IsNull(method)) { JSAtom atom; JSValue arg; switch(hint) { case HINT_STRING: atom = JS_ATOM_string; break; case HINT_NUMBER: atom = JS_ATOM_number; break; default: case HINT_NONE: atom = JS_ATOM_default; break; } arg = JS_AtomToString(ctx, atom); ret = JS_CallFree(ctx, method, val, 1, (JSValueConst *)&arg); JS_FreeValue(ctx, arg); if (JS_IsException(ret)) goto exception; JS_FreeValue(ctx, val); if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) return ret; JS_FreeValue(ctx, ret); return JS_ThrowTypeError(ctx, "toPrimitive"); } } if (hint != HINT_STRING) hint = HINT_NUMBER; for(i = 0; i < 2; i++) { if ((i ^ hint) == 0) { method_name = JS_ATOM_toString; } else { method_name = JS_ATOM_valueOf; } method = JS_GetProperty(ctx, val, method_name); if (JS_IsException(method)) goto exception; if (JS_IsFunction(ctx, method)) { ret = JS_CallFree(ctx, method, val, 0, NULL); if (JS_IsException(ret)) goto exception; if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { JS_FreeValue(ctx, val); return ret; } JS_FreeValue(ctx, ret); } else { JS_FreeValue(ctx, method); } } JS_ThrowTypeError(ctx, "toPrimitive"); exception: JS_FreeValue(ctx, val); return JS_EXCEPTION; } 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: case JS_TAG_UNDEFINED: 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; } #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); BOOL ret; ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; JS_FreeValue(ctx, val); return ret; } case JS_TAG_BIG_DECIMAL: { JSBigDecimal *p = JS_VALUE_GET_PTR(val); BOOL ret; ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; JS_FreeValue(ctx, val); return ret; } #endif 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; } /* XXX: remove */ static double js_strtod(const char *p, int radix, BOOL is_float) { double d; int c; if (!is_float || radix != 10) { uint64_t n_max, n; int int_exp, is_neg; is_neg = 0; if (*p == '-') { is_neg = 1; p++; } /* skip leading zeros */ while (*p == '0') p++; n = 0; if (radix == 10) n_max = ((uint64_t)-1 - 9) / 10; /* most common case */ else n_max = ((uint64_t)-1 - (radix - 1)) / radix; /* XXX: could be more precise */ int_exp = 0; while (*p != '\0') { c = to_digit((uint8_t)*p); if (c >= radix) break; if (n <= n_max) { n = n * radix + c; } else { int_exp++; } p++; } d = n; if (int_exp != 0) { d *= pow(radix, int_exp); } if (is_neg) d = -d; } else { d = strtod(p, NULL); } return d; } #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) #define ATOD_TYPE_BIG_FLOAT (2 << 7) #define ATOD_TYPE_BIG_DECIMAL (3 << 7) /* assume bigint mode: floats are parsed as integers if no decimal point nor exponent */ #define ATOD_MODE_BIGINT (1 << 9) /* accept -0x1 */ #define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) #ifdef CONFIG_BIGNUM static JSValue js_string_to_bigint(JSContext *ctx, const char *buf, int radix, int flags, slimb_t *pexponent) { bf_t a_s, *a = &a_s; int ret; JSValue val; val = JS_NewBigInt(ctx); if (JS_IsException(val)) return val; a = JS_GetBigInt(val); ret = bf_atof(a, buf, NULL, radix, BF_PREC_INF, BF_RNDZ); if (ret & BF_ST_MEM_ERROR) { JS_FreeValue(ctx, val); return JS_ThrowOutOfMemory(ctx); } val = JS_CompactBigInt1(ctx, val, (flags & ATOD_MODE_BIGINT) != 0); return val; } static JSValue js_string_to_bigfloat(JSContext *ctx, const char *buf, int radix, int flags, slimb_t *pexponent) { bf_t *a; int ret; JSValue val; val = JS_NewBigFloat(ctx); if (JS_IsException(val)) return val; a = JS_GetBigFloat(val); if (flags & ATOD_ACCEPT_SUFFIX) { /* return the exponent to get infinite precision */ ret = bf_atof2(a, pexponent, buf, NULL, radix, BF_PREC_INF, BF_RNDZ | BF_ATOF_EXPONENT); } else { ret = bf_atof(a, buf, NULL, radix, ctx->fp_env.prec, ctx->fp_env.flags); } if (ret & BF_ST_MEM_ERROR) { JS_FreeValue(ctx, val); return JS_ThrowOutOfMemory(ctx); } return val; } static JSValue js_string_to_bigdecimal(JSContext *ctx, const char *buf, int radix, int flags, slimb_t *pexponent) { bfdec_t *a; int ret; JSValue val; val = JS_NewBigDecimal(ctx); if (JS_IsException(val)) return val; a = JS_GetBigDecimal(val); ret = bfdec_atof(a, buf, NULL, BF_PREC_INF, BF_RNDZ | BF_ATOF_NO_NAN_INF); if (ret & BF_ST_MEM_ERROR) { JS_FreeValue(ctx, val); return JS_ThrowOutOfMemory(ctx); } return val; } #endif /* return an exception in case of memory error. Return JS_NAN if invalid syntax */ #ifdef CONFIG_BIGNUM static JSValue js_atof2(JSContext *ctx, const char *str, const char **pp, int radix, int flags, slimb_t *pexponent) #else static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, int radix, int flags) #endif { const char *p, *p_start; int sep, is_neg; BOOL is_float, has_legacy_octal; int atod_type = flags & ATOD_TYPE_MASK; char buf1[64], *buf; int i, j, len; BOOL buf_allocated = FALSE; JSValue val; /* optional separator between digits */ sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; has_legacy_octal = FALSE; 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; has_legacy_octal = TRUE; 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 || atod_type == ATOD_TYPE_BIG_FLOAT) && strstart(p, "Infinity", &p)) { #ifdef CONFIG_BIGNUM if (atod_type == ATOD_TYPE_BIG_FLOAT) { bf_t *a; val = JS_NewBigFloat(ctx); if (JS_IsException(val)) goto done; a = JS_GetBigFloat(val); bf_set_inf(a, is_neg); } else #endif { 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++; 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'; #ifdef CONFIG_BIGNUM if (flags & ATOD_ACCEPT_SUFFIX) { if (*p == 'n') { p++; atod_type = ATOD_TYPE_BIG_INT; } else if (*p == 'l') { p++; atod_type = ATOD_TYPE_BIG_FLOAT; } else if (*p == 'm') { p++; atod_type = ATOD_TYPE_BIG_DECIMAL; } else { if (flags & ATOD_MODE_BIGINT) { if (!is_float) atod_type = ATOD_TYPE_BIG_INT; if (has_legacy_octal) goto fail; } else { if (is_float && radix != 10) goto fail; } } } else { if (atod_type == ATOD_TYPE_FLOAT64) { if (flags & ATOD_MODE_BIGINT) { if (!is_float) atod_type = ATOD_TYPE_BIG_INT; if (has_legacy_octal) goto fail; } else { if (is_float && radix != 10) goto fail; } } } switch(atod_type) { case ATOD_TYPE_FLOAT64: { double d; d = js_strtod(buf, radix, is_float); /* return int or float64 */ val = JS_NewFloat64(ctx, d); } break; case ATOD_TYPE_BIG_INT: if (has_legacy_octal || is_float) goto fail; val = ctx->rt->bigint_ops.from_string(ctx, buf, radix, flags, NULL); break; case ATOD_TYPE_BIG_FLOAT: if (has_legacy_octal) goto fail; val = ctx->rt->bigfloat_ops.from_string(ctx, buf, radix, flags, pexponent); break; case ATOD_TYPE_BIG_DECIMAL: if (radix != 10) goto fail; val = ctx->rt->bigdecimal_ops.from_string(ctx, buf, radix, flags, NULL); break; default: abort(); } #else { double d; (void)has_legacy_octal; if (is_float && radix != 10) goto fail; d = js_strtod(buf, radix, is_float); val = JS_NewFloat64(ctx, d); } #endif 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; } #ifdef CONFIG_BIGNUM static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, int radix, int flags) { return js_atof2(ctx, str, pp, radix, flags, NULL); } #endif 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) { #ifdef CONFIG_BIGNUM case JS_TAG_BIG_DECIMAL: if (flag != TON_FLAG_NUMERIC) { JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert bigdecimal to number"); } ret = val; break; case JS_TAG_BIG_INT: if (flag != TON_FLAG_NUMERIC) { JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert bigint to number"); } ret = val; break; case JS_TAG_BIG_FLOAT: if (flag != TON_FLAG_NUMERIC) { JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert bigfloat to number"); } ret = val; break; #endif 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_UNDEFINED: ret = JS_NAN; 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: { 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)) { *pres = JS_FLOAT64_NAN; return -1; } 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; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); /* XXX: there can be a double rounding issue with some primitives (such as JS_ToUint8ClampFree()), but it is not critical to fix it. */ bf_get_float64(&p->num, &d, BF_RNDN); JS_FreeValue(ctx, val); } break; #endif default: abort(); } *pres = d; return 0; } 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: case JS_TAG_UNDEFINED: 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 */ /* XXX: should not be done here ? */ d = trunc(d) + 0.0; ret = JS_NewFloat64(ctx, d); } } break; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: { bf_t a_s, *a, r_s, *r = &r_s; BOOL is_nan; a = JS_ToBigFloat(ctx, &a_s, val); if (!bf_is_finite(a)) { is_nan = bf_is_nan(a); if (is_nan) ret = JS_NewInt32(ctx, 0); else ret = JS_DupValue(ctx, val); } else { ret = JS_NewBigInt(ctx); if (!JS_IsException(ret)) { r = JS_GetBigInt(ret); bf_set(r, a); bf_rint(r, BF_RNDZ); ret = JS_CompactBigInt(ctx, ret); } } if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, val); } break; #endif 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: case JS_TAG_UNDEFINED: 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; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); bf_get_int32(&ret, &p->num, 0); JS_FreeValue(ctx, val); } break; #endif 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: case JS_TAG_UNDEFINED: *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 > INT64_MAX) *pres = INT64_MAX; else *pres = (int64_t)d; } } return 0; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); bf_get_int64(pres, &p->num, 0); JS_FreeValue(ctx, val); } return 0; #endif 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: case JS_TAG_UNDEFINED: 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; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); bf_get_int64(&ret, &p->num, BF_GET_INT_MOD); JS_FreeValue(ctx, val); } break; #endif 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: case JS_TAG_UNDEFINED: 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; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); bf_get_int32(&ret, &p->num, BF_GET_INT_MOD); JS_FreeValue(ctx, val); } break; #endif 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 int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) { uint32_t tag; int res; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: case JS_TAG_UNDEFINED: res = JS_VALUE_GET_INT(val); #ifdef CONFIG_BIGNUM int_clamp: #endif res = max_int(0, min_int(255, res)); break; case JS_TAG_FLOAT64: { double d = JS_VALUE_GET_FLOAT64(val); if (isnan(d)) { res = 0; } else { if (d < 0) res = 0; else if (d > 255) res = 255; else res = lrint(d); } } break; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); bf_t r_s, *r = &r_s; bf_init(ctx->bf_ctx, r); bf_set(r, &p->num); bf_rint(r, BF_RNDN); bf_get_int32(&res, r, 0); bf_delete(r); JS_FreeValue(ctx, val); } goto int_clamp; #endif default: val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) { *pres = 0; return -1; } goto redo; } *pres = res; return 0; } static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, JSValue val) { uint32_t tag, len; redo: 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; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); bf_t a; BOOL res; bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD); bf_init(ctx->bf_ctx, &a); bf_set_ui(&a, len); res = bf_cmp_eq(&a, &p->num); bf_delete(&a); JS_FreeValue(ctx, val); if (!res) goto fail; } break; #endif default: if (JS_TAG_IS_FLOAT64(tag)) { double d; d = JS_VALUE_GET_FLOAT64(val); len = (uint32_t)d; if (len != d) { fail: JS_ThrowRangeError(ctx, "invalid array length"); return -1; } } else { val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) return -1; goto redo; } break; } *plen = len; return 0; } #define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) static BOOL is_safe_integer(double d) { return isfinite(d) && floor(d) == d && fabs(d) <= (double)MAX_SAFE_INTEGER; } 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; } /* Note: can return an exception */ static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val) { double d; if (!JS_IsNumber(val)) return FALSE; if (unlikely(JS_ToFloat64(ctx, &d, val))) return -1; return isfinite(d) && floor(d) == d; } static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val) { uint32_t tag; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: { int v; v = JS_VALUE_GET_INT(val); return (v < 0); } case JS_TAG_FLOAT64: { JSFloat64Union u; u.d = JS_VALUE_GET_FLOAT64(val); return (u.u64 >> 63); } #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); /* Note: integer zeros are not necessarily positive */ return p->num.sign && !bf_is_zero(&p->num); } case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); return p->num.sign; } break; case JS_TAG_BIG_DECIMAL: { JSBigDecimal *p = JS_VALUE_GET_PTR(val); return p->num.sign; } break; #endif default: return FALSE; } } #ifdef CONFIG_BIGNUM static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) { JSValue ret; bf_t a_s, *a; char *str; int saved_sign; a = JS_ToBigInt(ctx, &a_s, val); if (!a) return JS_EXCEPTION; saved_sign = a->sign; if (a->expn == BF_EXP_ZERO) a->sign = 0; str = bf_ftoa(NULL, a, radix, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC | BF_FTOA_JS_QUIRKS); a->sign = saved_sign; JS_FreeBigInt(ctx, a, &a_s); if (!str) return JS_ThrowOutOfMemory(ctx); ret = JS_NewString(ctx, str); bf_free(ctx->bf_ctx, str); return ret; } static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) { return js_bigint_to_string1(ctx, val, 10); } static JSValue js_ftoa(JSContext *ctx, JSValueConst val1, int radix, limb_t prec, bf_flags_t flags) { JSValue val, ret; bf_t a_s, *a; char *str; int saved_sign; val = JS_ToNumeric(ctx, val1); if (JS_IsException(val)) return val; a = JS_ToBigFloat(ctx, &a_s, val); saved_sign = a->sign; if (a->expn == BF_EXP_ZERO) a->sign = 0; flags |= BF_FTOA_JS_QUIRKS; if ((flags & BF_FTOA_FORMAT_MASK) == BF_FTOA_FORMAT_FREE_MIN) { /* Note: for floating point numbers with a radix which is not a power of two, the current precision is used to compute the number of digits. */ if ((radix & (radix - 1)) != 0) { bf_t r_s, *r = &r_s; int prec, flags1; /* must round first */ if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) { prec = ctx->fp_env.prec; flags1 = ctx->fp_env.flags & (BF_FLAG_SUBNORMAL | (BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT)); } else { prec = 53; flags1 = bf_set_exp_bits(11) | BF_FLAG_SUBNORMAL; } bf_init(ctx->bf_ctx, r); bf_set(r, a); bf_round(r, prec, flags1 | BF_RNDN); str = bf_ftoa(NULL, r, radix, prec, flags1 | flags); bf_delete(r); } else { str = bf_ftoa(NULL, a, radix, BF_PREC_INF, flags); } } else { str = bf_ftoa(NULL, a, radix, prec, flags); } a->sign = saved_sign; if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, val); if (!str) return JS_ThrowOutOfMemory(ctx); ret = JS_NewString(ctx, str); bf_free(ctx->bf_ctx, str); return ret; } static JSValue js_bigfloat_to_string(JSContext *ctx, JSValueConst val) { return js_ftoa(ctx, val, 10, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN); } static JSValue js_bigdecimal_to_string1(JSContext *ctx, JSValueConst val, limb_t prec, int flags) { JSValue ret; bfdec_t *a; char *str; int saved_sign; a = JS_ToBigDecimal(ctx, val); saved_sign = a->sign; if (a->expn == BF_EXP_ZERO) a->sign = 0; str = bfdec_ftoa(NULL, a, prec, flags | BF_FTOA_JS_QUIRKS); a->sign = saved_sign; if (!str) return JS_ThrowOutOfMemory(ctx); ret = JS_NewString(ctx, str); bf_free(ctx->bf_ctx, str); return ret; } static JSValue js_bigdecimal_to_string(JSContext *ctx, JSValueConst val) { return js_bigdecimal_to_string1(ctx, val, 0, BF_RNDZ | BF_FTOA_FORMAT_FREE); } #endif /* CONFIG_BIGNUM */ /* 2 <= base <= 36 */ static char *i64toa(char *buf_end, int64_t n, unsigned int base) { char *q = buf_end; int digit, is_neg; is_neg = 0; if (n < 0) { is_neg = 1; n = -n; } *--q = '\0'; do { digit = (uint64_t)n % base; n = (uint64_t)n / base; if (digit < 10) digit += '0'; else digit += 'a' - 10; *--q = digit; } while (n != 0); if (is_neg) *--q = '-'; return q; } /* buf1 contains the printf result */ static void js_ecvt1(double d, int n_digits, int *decpt, int *sign, char *buf, int rounding_mode, char *buf1, int buf1_size) { if (rounding_mode != FE_TONEAREST) fesetround(rounding_mode); snprintf(buf1, buf1_size, "%+.*e", n_digits - 1, d); if (rounding_mode != FE_TONEAREST) fesetround(FE_TONEAREST); *sign = (buf1[0] == '-'); /* mantissa */ buf[0] = buf1[1]; if (n_digits > 1) memcpy(buf + 1, buf1 + 3, n_digits - 1); buf[n_digits] = '\0'; /* exponent */ *decpt = atoi(buf1 + n_digits + 2 + (n_digits > 1)) + 1; } /* maximum buffer size for js_dtoa */ #define JS_DTOA_BUF_SIZE 128 /* needed because ecvt usually limits the number of digits to 17. Return the number of digits. */ static int js_ecvt(double d, int n_digits, int *decpt, int *sign, char *buf, BOOL is_fixed) { int rounding_mode; char buf_tmp[JS_DTOA_BUF_SIZE]; if (!is_fixed) { unsigned int n_digits_min, n_digits_max; /* find the minimum amount of digits (XXX: inefficient but simple) */ n_digits_min = 1; n_digits_max = 17; while (n_digits_min < n_digits_max) { n_digits = (n_digits_min + n_digits_max) / 2; js_ecvt1(d, n_digits, decpt, sign, buf, FE_TONEAREST, buf_tmp, sizeof(buf_tmp)); if (strtod(buf_tmp, NULL) == d) { /* no need to keep the trailing zeros */ while (n_digits >= 2 && buf[n_digits - 1] == '0') n_digits--; n_digits_max = n_digits; } else { n_digits_min = n_digits + 1; } } n_digits = n_digits_max; rounding_mode = FE_TONEAREST; } else { rounding_mode = FE_TONEAREST; #ifdef CONFIG_PRINTF_RNDN { char buf1[JS_DTOA_BUF_SIZE], buf2[JS_DTOA_BUF_SIZE]; int decpt1, sign1, decpt2, sign2; /* The JS rounding is specified as round to nearest ties away from zero (RNDNA), but in printf the "ties" case is not specified (for example it is RNDN for glibc, RNDNA for Windows), so we must round manually. */ js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_TONEAREST, buf_tmp, sizeof(buf_tmp)); /* XXX: could use 2 digits to reduce the average running time */ if (buf1[n_digits] == '5') { js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_DOWNWARD, buf_tmp, sizeof(buf_tmp)); js_ecvt1(d, n_digits + 1, &decpt2, &sign2, buf2, FE_UPWARD, buf_tmp, sizeof(buf_tmp)); if (memcmp(buf1, buf2, n_digits + 1) == 0 && decpt1 == decpt2) { /* exact result: round away from zero */ if (sign1) rounding_mode = FE_DOWNWARD; else rounding_mode = FE_UPWARD; } } } #endif /* CONFIG_PRINTF_RNDN */ } js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode, buf_tmp, sizeof(buf_tmp)); return n_digits; } static int js_fcvt1(char *buf, int buf_size, double d, int n_digits, int rounding_mode) { int n; if (rounding_mode != FE_TONEAREST) fesetround(rounding_mode); n = snprintf(buf, buf_size, "%.*f", n_digits, d); if (rounding_mode != FE_TONEAREST) fesetround(FE_TONEAREST); assert(n < buf_size); return n; } static void js_fcvt(char *buf, int buf_size, double d, int n_digits) { int rounding_mode; rounding_mode = FE_TONEAREST; #ifdef CONFIG_PRINTF_RNDN { int n1, n2; char buf1[JS_DTOA_BUF_SIZE]; char buf2[JS_DTOA_BUF_SIZE]; /* The JS rounding is specified as round to nearest ties away from zero (RNDNA), but in printf the "ties" case is not specified (for example it is RNDN for glibc, RNDNA for Windows), so we must round manually. */ n1 = js_fcvt1(buf1, sizeof(buf1), d, n_digits + 1, FE_TONEAREST); rounding_mode = FE_TONEAREST; /* XXX: could use 2 digits to reduce the average running time */ if (buf1[n1 - 1] == '5') { n1 = js_fcvt1(buf1, sizeof(buf1), d, n_digits + 1, FE_DOWNWARD); n2 = js_fcvt1(buf2, sizeof(buf2), d, n_digits + 1, FE_UPWARD); if (n1 == n2 && memcmp(buf1, buf2, n1) == 0) { /* exact result: round away from zero */ if (buf1[0] == '-') rounding_mode = FE_DOWNWARD; else rounding_mode = FE_UPWARD; } } } #endif /* CONFIG_PRINTF_RNDN */ js_fcvt1(buf, buf_size, d, n_digits, rounding_mode); } /* radix != 10 is only supported with flags = JS_DTOA_VAR_FORMAT */ /* use as many digits as necessary */ #define JS_DTOA_VAR_FORMAT (0 << 0) /* use n_digits significant digits (1 <= n_digits <= 101) */ #define JS_DTOA_FIXED_FORMAT (1 << 0) /* force fractional format: [-]dd.dd with n_digits fractional digits */ #define JS_DTOA_FRAC_FORMAT (2 << 0) /* force exponential notation either in fixed or variable format */ #define JS_DTOA_FORCE_EXP (1 << 2) /* XXX: slow and maybe not fully correct. Use libbf when it is fast enough. XXX: radix != 10 is only supported for small integers */ static void js_dtoa1(char *buf, double d, int radix, int n_digits, int flags) { char *q; if (!isfinite(d)) { if (isnan(d)) { strcpy(buf, "NaN"); } else { q = buf; if (d < 0) *q++ = '-'; strcpy(q, "Infinity"); } } else if (flags == JS_DTOA_VAR_FORMAT) { int64_t i64; char buf1[70], *ptr; i64 = (int64_t)d; if (d != i64 || i64 > MAX_SAFE_INTEGER || i64 < -MAX_SAFE_INTEGER) goto generic_conv; /* fast path for integers */ ptr = i64toa(buf1 + sizeof(buf1), i64, radix); strcpy(buf, ptr); } else { if (d == 0.0) d = 0.0; /* convert -0 to 0 */ if (flags == JS_DTOA_FRAC_FORMAT) { js_fcvt(buf, JS_DTOA_BUF_SIZE, d, n_digits); } else { char buf1[JS_DTOA_BUF_SIZE]; int sign, decpt, k, n, i, p, n_max; BOOL is_fixed; generic_conv: is_fixed = ((flags & 3) == JS_DTOA_FIXED_FORMAT); if (is_fixed) { n_max = n_digits; } else { n_max = 21; } /* the number has k digits (k >= 1) */ k = js_ecvt(d, n_digits, &decpt, &sign, buf1, is_fixed); n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */ q = buf; if (sign) *q++ = '-'; if (flags & JS_DTOA_FORCE_EXP) goto force_exp; if (n >= 1 && n <= n_max) { if (k <= n) { memcpy(q, buf1, k); q += k; for(i = 0; i < (n - k); i++) *q++ = '0'; *q = '\0'; } else { /* k > n */ memcpy(q, buf1, n); q += n; *q++ = '.'; for(i = 0; i < (k - n); i++) *q++ = buf1[n + i]; *q = '\0'; } } else if (n >= -5 && n <= 0) { *q++ = '0'; *q++ = '.'; for(i = 0; i < -n; i++) *q++ = '0'; memcpy(q, buf1, k); q += k; *q = '\0'; } else { force_exp: /* exponential notation */ *q++ = buf1[0]; if (k > 1) { *q++ = '.'; for(i = 1; i < k; i++) *q++ = buf1[i]; } *q++ = 'e'; p = n - 1; if (p >= 0) *q++ = '+'; sprintf(q, "%d", p); } } } } static JSValue js_dtoa(JSContext *ctx, double d, int radix, int n_digits, int flags) { char buf[JS_DTOA_BUF_SIZE]; js_dtoa1(buf, d, radix, n_digits, flags); return JS_NewString(ctx, buf); } JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey) { uint32_t tag; const char *str; char buf[32]; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_STRING: return JS_DupValue(ctx, val); case JS_TAG_INT: snprintf(buf, sizeof(buf), "%d", JS_VALUE_GET_INT(val)); str = buf; goto new_string; 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_UNDEFINED: return JS_AtomToString(ctx, JS_ATOM_undefined); 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: str = "[function bytecode]"; goto new_string; 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_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, JS_DTOA_VAR_FORMAT); #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: return ctx->rt->bigint_ops.to_string(ctx, val); case JS_TAG_BIG_FLOAT: return ctx->rt->bigfloat_ops.to_string(ctx, val); case JS_TAG_BIG_DECIMAL: return ctx->rt->bigdecimal_ops.to_string(ctx, val); #endif default: str = "[unsupported type]"; new_string: return JS_NewString(ctx, str); } } 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_IsUndefined(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) { 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 || tag == JS_TAG_UNDEFINED) return JS_ThrowTypeError(ctx, "null or undefined are 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 || (c >= 0xd800 && c < 0xe000)) { 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; } static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) { printf("%14s %4s %4s %14s %10s %s\n", "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); } /* for debug only: dump an object without side effect */ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) { uint32_t i; char atom_buf[ATOM_GET_STR_BUF_SIZE]; JSShape *sh; JSShapeProperty *prs; JSProperty *pr; BOOL is_first = TRUE; /* 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 ", "-", "-"); } printf("%10s ", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); if (p->is_exotic && p->fast_array) { printf("[ "); for(i = 0; i < p->u.array.count; i++) { if (i != 0) printf(", "); switch (p->class_id) { case JS_CLASS_ARRAY: case JS_CLASS_ARGUMENTS: JS_DumpValueShort(rt, p->u.array.u.values[i]); break; case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_FLOAT64_ARRAY: { int size = 1 << typed_array_size_log2(p->class_id); const uint8_t *b = p->u.array.u.uint8_ptr + i * size; while (size-- > 0) printf("%02X", *b++); } break; } } printf(" ] "); } if (sh) { printf("{ "); for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { if (prs->atom != JS_ATOM_NULL) { pr = &p->prop[i]; if (!is_first) printf(", "); printf("%s: ", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { printf("[getset %p %p]", (void *)pr->u.getset.getter, (void *)pr->u.getset.setter); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { printf("[varref %p]", (void *)pr->u.var_ref); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { printf("[autoinit %p %p]", (void *)pr->u.init.init_func, (void *)pr->u.init.opaque); } else { JS_DumpValueShort(rt, pr->u.value); } is_first = FALSE; } } printf(" }"); } if (js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b = p->u.func.function_bytecode; JSVarRef **var_refs; if (b->closure_var_count) { var_refs = p->u.func.var_refs; printf(" Closure:"); for(i = 0; i < b->closure_var_count; i++) { printf(" "); JS_DumpValueShort(rt, var_refs[i]->value); } if (p->u.func.home_object) { printf(" HomeObject: "); JS_DumpValueShort(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); } } } 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_ASYNC_FUNCTION: printf("[async_function]"); break; default: printf("[unknown %d]", p->gc_obj_type); break; } printf("\n"); } } static __maybe_unused void JS_DumpValueShort(JSRuntime *rt, JSValueConst val) { uint32_t tag = JS_VALUE_GET_NORM_TAG(val); const char *str; switch(tag) { case JS_TAG_INT: printf("%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; case JS_TAG_UNDEFINED: str = "undefined"; print_str: printf("%s", str); break; case JS_TAG_FLOAT64: printf("%.14g", JS_VALUE_GET_FLOAT64(val)); break; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); char *str; str = bf_ftoa(NULL, &p->num, 10, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC); printf("%sn", str); bf_realloc(&rt->bf_ctx, str, 0); } break; case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); char *str; str = bf_ftoa(NULL, &p->num, 16, BF_PREC_INF, BF_RNDZ | BF_FTOA_FORMAT_FREE | BF_FTOA_ADD_PREFIX); printf("%sl", str); bf_free(&rt->bf_ctx, str); } break; case JS_TAG_BIG_DECIMAL: { JSBigDecimal *p = JS_VALUE_GET_PTR(val); char *str; str = bfdec_ftoa(NULL, &p->num, BF_PREC_INF, BF_RNDZ | BF_FTOA_FORMAT_FREE); printf("%sm", str); bf_free(&rt->bf_ctx, str); } break; #endif case JS_TAG_STRING: { JSString *p; p = JS_VALUE_GET_STRING(val); JS_DumpString(rt, p); } break; case JS_TAG_FUNCTION_BYTECODE: { JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); char buf[ATOM_GET_STR_BUF_SIZE]; printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); } break; case JS_TAG_OBJECT: { JSObject *p = JS_VALUE_GET_OBJ(val); JSAtom atom = rt->class_array[p->class_id].class_name; char atom_buf[ATOM_GET_STR_BUF_SIZE]; printf("[%s %p]", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); } break; case JS_TAG_SYMBOL: { JSAtomStruct *p = JS_VALUE_GET_PTR(val); char atom_buf[ATOM_GET_STR_BUF_SIZE]; printf("Symbol(%s)", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); } break; case JS_TAG_MODULE: printf("[module]"); break; default: printf("[unknown tag %d]", tag); break; } } static __maybe_unused void JS_DumpValue(JSContext *ctx, JSValueConst val) { JS_DumpValueShort(ctx->rt, val); } static __maybe_unused void JS_PrintValue(JSContext *ctx, const char *str, JSValueConst val) { printf("%s=", str); JS_DumpValueShort(ctx->rt, val); printf("\n"); } /* return -1 if exception (proxy case) or TRUE/FALSE */ int JS_IsArray(JSContext *ctx, JSValueConst val) { JSObject *p; if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ(val); if (unlikely(p->class_id == JS_CLASS_PROXY)) return js_proxy_isArray(ctx, val); else 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); } } #ifdef CONFIG_BIGNUM JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v) { JSValue val; bf_t *a; val = JS_NewBigInt(ctx); if (JS_IsException(val)) return val; a = JS_GetBigInt(val); if (bf_set_si(a, v)) { JS_FreeValue(ctx, val); return JS_ThrowOutOfMemory(ctx); } return val; } JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) { if (is_math_mode(ctx) && v >= -MAX_SAFE_INTEGER && v <= MAX_SAFE_INTEGER) { return JS_NewInt64(ctx, v); } else { return JS_NewBigInt64_1(ctx, v); } } JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) { JSValue val; if (is_math_mode(ctx) && v <= MAX_SAFE_INTEGER) { val = JS_NewInt64(ctx, v); } else { bf_t *a; val = JS_NewBigInt(ctx); if (JS_IsException(val)) return val; a = JS_GetBigInt(val); if (bf_set_ui(a, v)) { JS_FreeValue(ctx, val); return JS_ThrowOutOfMemory(ctx); } } return val; } /* if the returned bigfloat is allocated it is equal to 'buf'. Otherwise it is a pointer to the bigfloat in 'val'. Return NULL in case of error. */ static bf_t *JS_ToBigFloat(JSContext *ctx, bf_t *buf, JSValueConst val) { uint32_t tag; bf_t *r; JSBigFloat *p; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: r = buf; bf_init(ctx->bf_ctx, r); if (bf_set_si(r, JS_VALUE_GET_INT(val))) goto fail; break; case JS_TAG_FLOAT64: r = buf; bf_init(ctx->bf_ctx, r); if (bf_set_float64(r, JS_VALUE_GET_FLOAT64(val))) { fail: bf_delete(r); return NULL; } break; case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: p = JS_VALUE_GET_PTR(val); r = &p->num; break; case JS_TAG_UNDEFINED: default: r = buf; bf_init(ctx->bf_ctx, r); bf_set_nan(r); break; } return r; } /* return NULL if invalid type */ static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val) { uint32_t tag; JSBigDecimal *p; bfdec_t *r; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_BIG_DECIMAL: p = JS_VALUE_GET_PTR(val); r = &p->num; break; default: JS_ThrowTypeError(ctx, "bigdecimal expected"); r = NULL; break; } return r; } /* return NaN if bad bigint literal */ static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) { const char *str, *p; size_t len; int flags; 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) { val = JS_NewBigInt64(ctx, 0); } else { flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; if (is_math_mode(ctx)) flags |= ATOD_MODE_BIGINT; val = js_atof(ctx, p, &p, 0, flags); p += skip_spaces(p); if (!JS_IsException(val)) { if ((p - str) != len) { JS_FreeValue(ctx, val); val = JS_NAN; } } } JS_FreeCString(ctx, str); return val; } static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) { val = JS_StringToBigInt(ctx, val); if (JS_VALUE_IS_NAN(val)) return JS_ThrowSyntaxError(ctx, "invalid bigint literal"); return val; } /* if the returned bigfloat is allocated it is equal to 'buf'. Otherwise it is a pointer to the bigfloat in 'val'. */ static bf_t *JS_ToBigIntFree(JSContext *ctx, bf_t *buf, JSValue val) { uint32_t tag; bf_t *r; JSBigFloat *p; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_NULL: case JS_TAG_UNDEFINED: if (!is_math_mode(ctx)) goto fail; /* fall tru */ case JS_TAG_BOOL: r = buf; bf_init(ctx->bf_ctx, r); bf_set_si(r, JS_VALUE_GET_INT(val)); break; case JS_TAG_FLOAT64: { double d = JS_VALUE_GET_FLOAT64(val); if (!is_math_mode(ctx)) goto fail; if (!isfinite(d)) goto fail; r = buf; bf_init(ctx->bf_ctx, r); d = trunc(d); bf_set_float64(r, d); } break; case JS_TAG_BIG_INT: p = JS_VALUE_GET_PTR(val); r = &p->num; break; case JS_TAG_BIG_FLOAT: if (!is_math_mode(ctx)) goto fail; p = JS_VALUE_GET_PTR(val); if (!bf_is_finite(&p->num)) goto fail; r = buf; bf_init(ctx->bf_ctx, r); bf_set(r, &p->num); bf_rint(r, BF_RNDZ); JS_FreeValue(ctx, val); break; case JS_TAG_STRING: val = JS_StringToBigIntErr(ctx, val); if (JS_IsException(val)) return NULL; goto redo; case JS_TAG_OBJECT: val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); if (JS_IsException(val)) return NULL; goto redo; default: fail: JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "cannot convert to bigint"); return NULL; } return r; } static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val) { return JS_ToBigIntFree(ctx, buf, JS_DupValue(ctx, val)); } static __maybe_unused JSValue JS_ToBigIntValueFree(JSContext *ctx, JSValue val) { if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT) { return val; } else { bf_t a_s, *a, *r; int ret; JSValue res; res = JS_NewBigInt(ctx); if (JS_IsException(res)) return JS_EXCEPTION; a = JS_ToBigIntFree(ctx, &a_s, val); if (!a) { JS_FreeValue(ctx, res); return JS_EXCEPTION; } r = JS_GetBigInt(res); ret = bf_set(r, a); JS_FreeBigInt(ctx, a, &a_s); if (ret) { JS_FreeValue(ctx, res); return JS_ThrowOutOfMemory(ctx); } return JS_CompactBigInt(ctx, res); } } /* free the bf_t allocated by JS_ToBigInt */ static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf) { if (a == buf) { bf_delete(a); } else { JSBigFloat *p = (JSBigFloat *)((uint8_t *)a - offsetof(JSBigFloat, num)); JS_FreeValue(ctx, JS_MKPTR(JS_TAG_BIG_FLOAT, p)); } } /* XXX: merge with JS_ToInt64Free with a specific flag */ static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) { bf_t a_s, *a; a = JS_ToBigIntFree(ctx, &a_s, val); if (!a) { *pres = 0; return -1; } bf_get_int64(pres, a, BF_GET_INT_MOD); JS_FreeBigInt(ctx, a, &a_s); return 0; } int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) { return JS_ToBigInt64Free(ctx, pres, JS_DupValue(ctx, val)); } static JSBigFloat *js_new_bf(JSContext *ctx) { JSBigFloat *p; p = js_malloc(ctx, sizeof(*p)); if (!p) return NULL; p->header.ref_count = 1; bf_init(ctx->bf_ctx, &p->num); return p; } static JSValue JS_NewBigFloat(JSContext *ctx) { JSBigFloat *p; p = js_malloc(ctx, sizeof(*p)); if (!p) return JS_EXCEPTION; p->header.ref_count = 1; bf_init(ctx->bf_ctx, &p->num); return JS_MKPTR(JS_TAG_BIG_FLOAT, p); } static JSValue JS_NewBigDecimal(JSContext *ctx) { JSBigDecimal *p; p = js_malloc(ctx, sizeof(*p)); if (!p) return JS_EXCEPTION; p->header.ref_count = 1; bfdec_init(ctx->bf_ctx, &p->num); return JS_MKPTR(JS_TAG_BIG_DECIMAL, p); } static JSValue JS_NewBigInt(JSContext *ctx) { JSBigFloat *p; p = js_malloc(ctx, sizeof(*p)); if (!p) return JS_EXCEPTION; p->header.ref_count = 1; bf_init(ctx->bf_ctx, &p->num); return JS_MKPTR(JS_TAG_BIG_INT, p); } static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val, BOOL convert_to_safe_integer) { int64_t v; bf_t *a; if (JS_VALUE_GET_TAG(val) != JS_TAG_BIG_INT) return val; /* fail safe */ a = JS_GetBigInt(val); if (convert_to_safe_integer && bf_get_int64(&v, a, 0) == 0 && v >= -MAX_SAFE_INTEGER && v <= MAX_SAFE_INTEGER) { JS_FreeValue(ctx, val); return JS_NewInt64(ctx, v); } else if (a->expn == BF_EXP_ZERO && a->sign) { JSBigFloat *p = JS_VALUE_GET_PTR(val); assert(p->header.ref_count == 1); a->sign = 0; } return val; } /* Convert the big int to a safe integer if in math mode. normalize the zero representation. Could also be used to convert the bigint to a short bigint value. The reference count of the value must be 1. Cannot fail */ static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val) { return JS_CompactBigInt1(ctx, val, is_math_mode(ctx)); } /* must be kept in sync with JSOverloadableOperatorEnum */ static const char *js_overloadable_operator_names[JS_OVOP_COUNT] = { "+", "-", "*", "/", "%", "**", "|", "&", "^", "<<", ">>", ">>>", "==", "<", "pos", "neg", "++", "--", "~", }; static int get_ovop_from_opcode(OPCodeEnum op) { switch(op) { case OP_add: return JS_OVOP_ADD; case OP_sub: return JS_OVOP_SUB; case OP_mul: return JS_OVOP_MUL; case OP_div: return JS_OVOP_DIV; case OP_mod: case OP_math_mod: return JS_OVOP_MOD; case OP_pow: return JS_OVOP_POW; case OP_or: return JS_OVOP_OR; case OP_and: return JS_OVOP_AND; case OP_xor: return JS_OVOP_XOR; case OP_shl: return JS_OVOP_SHL; case OP_sar: return JS_OVOP_SAR; case OP_shr: return JS_OVOP_SHR; case OP_eq: case OP_neq: return JS_OVOP_EQ; case OP_lt: case OP_lte: case OP_gt: case OP_gte: return JS_OVOP_LESS; case OP_plus: return JS_OVOP_POS; case OP_neg: return JS_OVOP_NEG; case OP_inc: return JS_OVOP_INC; case OP_dec: return JS_OVOP_DEC; default: abort(); } } /* return NULL if not present */ static JSObject *find_binary_op(JSBinaryOperatorDef *def, uint32_t operator_index, JSOverloadableOperatorEnum op) { JSBinaryOperatorDefEntry *ent; int i; for(i = 0; i < def->count; i++) { ent = &def->tab[i]; if (ent->operator_index == operator_index) return ent->ops[op]; } return NULL; } /* return -1 if exception, 0 if no operator overloading, 1 if overloaded operator called */ static __exception int js_call_binary_op_fallback(JSContext *ctx, JSValue *pret, JSValueConst op1, JSValueConst op2, OPCodeEnum op, BOOL is_numeric, int hint) { JSValue opset1_obj, opset2_obj, method, ret, new_op1, new_op2; JSOperatorSetData *opset1, *opset2; JSOverloadableOperatorEnum ovop; JSObject *p; JSValueConst args[2]; if (!ctx->allow_operator_overloading) return 0; opset2_obj = JS_UNDEFINED; opset1_obj = JS_GetProperty(ctx, op1, JS_ATOM_Symbol_operatorSet); if (JS_IsException(opset1_obj)) goto exception; if (JS_IsUndefined(opset1_obj)) return 0; opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); if (!opset1) goto exception; opset2_obj = JS_GetProperty(ctx, op2, JS_ATOM_Symbol_operatorSet); if (JS_IsException(opset2_obj)) goto exception; if (JS_IsUndefined(opset2_obj)) { JS_FreeValue(ctx, opset1_obj); return 0; } opset2 = JS_GetOpaque2(ctx, opset2_obj, JS_CLASS_OPERATOR_SET); if (!opset2) goto exception; if (opset1->is_primitive && opset2->is_primitive) { JS_FreeValue(ctx, opset1_obj); JS_FreeValue(ctx, opset2_obj); return 0; } ovop = get_ovop_from_opcode(op); if (opset1->operator_counter == opset2->operator_counter) { p = opset1->self_ops[ovop]; } else if (opset1->operator_counter > opset2->operator_counter) { p = find_binary_op(&opset1->left, opset2->operator_counter, ovop); } else { p = find_binary_op(&opset2->right, opset1->operator_counter, ovop); } if (!p) { JS_ThrowTypeError(ctx, "operator %s: no function defined", js_overloadable_operator_names[ovop]); goto exception; } if (opset1->is_primitive) { if (is_numeric) { new_op1 = JS_ToNumeric(ctx, op1); } else { new_op1 = JS_ToPrimitive(ctx, op1, hint); } if (JS_IsException(new_op1)) goto exception; } else { new_op1 = JS_DupValue(ctx, op1); } if (opset2->is_primitive) { if (is_numeric) { new_op2 = JS_ToNumeric(ctx, op2); } else { new_op2 = JS_ToPrimitive(ctx, op2, hint); } if (JS_IsException(new_op2)) { JS_FreeValue(ctx, new_op1); goto exception; } } else { new_op2 = JS_DupValue(ctx, op2); } /* XXX: could apply JS_ToPrimitive() if primitive type so that the operator function does not get a value object */ method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); if (ovop == JS_OVOP_LESS && (op == OP_lte || op == OP_gt)) { args[0] = new_op2; args[1] = new_op1; } else { args[0] = new_op1; args[1] = new_op2; } ret = JS_CallFree(ctx, method, JS_UNDEFINED, 2, args); JS_FreeValue(ctx, new_op1); JS_FreeValue(ctx, new_op2); if (JS_IsException(ret)) goto exception; if (ovop == JS_OVOP_EQ) { BOOL res = JS_ToBoolFree(ctx, ret); if (op == OP_neq) res ^= 1; ret = JS_NewBool(ctx, res); } else if (ovop == JS_OVOP_LESS) { if (JS_IsUndefined(ret)) { ret = JS_FALSE; } else { BOOL res = JS_ToBoolFree(ctx, ret); if (op == OP_lte || op == OP_gte) res ^= 1; ret = JS_NewBool(ctx, res); } } JS_FreeValue(ctx, opset1_obj); JS_FreeValue(ctx, opset2_obj); *pret = ret; return 1; exception: JS_FreeValue(ctx, opset1_obj); JS_FreeValue(ctx, opset2_obj); *pret = JS_UNDEFINED; return -1; } /* try to call the operation on the operatorSet field of 'obj'. Only used for "/" and "**" on the BigInt prototype in math mode */ static __exception int js_call_binary_op_simple(JSContext *ctx, JSValue *pret, JSValueConst obj, JSValueConst op1, JSValueConst op2, OPCodeEnum op) { JSValue opset1_obj, method, ret, new_op1, new_op2; JSOperatorSetData *opset1; JSOverloadableOperatorEnum ovop; JSObject *p; JSValueConst args[2]; opset1_obj = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_operatorSet); if (JS_IsException(opset1_obj)) goto exception; if (JS_IsUndefined(opset1_obj)) return 0; opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); if (!opset1) goto exception; ovop = get_ovop_from_opcode(op); p = opset1->self_ops[ovop]; if (!p) { JS_FreeValue(ctx, opset1_obj); return 0; } new_op1 = JS_ToNumeric(ctx, op1); if (JS_IsException(new_op1)) goto exception; new_op2 = JS_ToNumeric(ctx, op2); if (JS_IsException(new_op2)) { JS_FreeValue(ctx, new_op1); goto exception; } method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); args[0] = new_op1; args[1] = new_op2; ret = JS_CallFree(ctx, method, JS_UNDEFINED, 2, args); JS_FreeValue(ctx, new_op1); JS_FreeValue(ctx, new_op2); if (JS_IsException(ret)) goto exception; JS_FreeValue(ctx, opset1_obj); *pret = ret; return 1; exception: JS_FreeValue(ctx, opset1_obj); *pret = JS_UNDEFINED; return -1; } /* return -1 if exception, 0 if no operator overloading, 1 if overloaded operator called */ static __exception int js_call_unary_op_fallback(JSContext *ctx, JSValue *pret, JSValueConst op1, OPCodeEnum op) { JSValue opset1_obj, method, ret; JSOperatorSetData *opset1; JSOverloadableOperatorEnum ovop; JSObject *p; if (!ctx->allow_operator_overloading) return 0; opset1_obj = JS_GetProperty(ctx, op1, JS_ATOM_Symbol_operatorSet); if (JS_IsException(opset1_obj)) goto exception; if (JS_IsUndefined(opset1_obj)) return 0; opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); if (!opset1) goto exception; if (opset1->is_primitive) { JS_FreeValue(ctx, opset1_obj); return 0; } ovop = get_ovop_from_opcode(op); p = opset1->self_ops[ovop]; if (!p) { JS_ThrowTypeError(ctx, "no overloaded operator %s", js_overloadable_operator_names[ovop]); goto exception; } method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); ret = JS_CallFree(ctx, method, JS_UNDEFINED, 1, &op1); if (JS_IsException(ret)) goto exception; JS_FreeValue(ctx, opset1_obj); *pret = ret; return 1; exception: JS_FreeValue(ctx, opset1_obj); *pret = JS_UNDEFINED; return -1; } static JSValue throw_bf_exception(JSContext *ctx, int status) { const char *str; if (status & BF_ST_MEM_ERROR) return JS_ThrowOutOfMemory(ctx); if (status & BF_ST_DIVIDE_ZERO) { str = "division by zero"; } else if (status & BF_ST_INVALID_OP) { str = "invalid operation"; } else { str = "integer overflow"; } return JS_ThrowRangeError(ctx, "%s", str); } static int js_unary_arith_bigint(JSContext *ctx, JSValue *pres, OPCodeEnum op, JSValue op1) { bf_t a_s, *r, *a; int ret, v; JSValue res; if (op == OP_plus && !is_math_mode(ctx)) { JS_ThrowTypeError(ctx, "bigint argument with unary +"); JS_FreeValue(ctx, op1); return -1; } res = JS_NewBigInt(ctx); if (JS_IsException(res)) { JS_FreeValue(ctx, op1); return -1; } r = JS_GetBigInt(res); a = JS_ToBigInt(ctx, &a_s, op1); ret = 0; switch(op) { case OP_inc: case OP_dec: v = 2 * (op - OP_dec) - 1; ret = bf_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); break; case OP_plus: ret = bf_set(r, a); break; case OP_neg: ret = bf_set(r, a); bf_neg(r); break; case OP_not: ret = bf_add_si(r, a, 1, BF_PREC_INF, BF_RNDZ); bf_neg(r); break; default: abort(); } JS_FreeBigInt(ctx, a, &a_s); JS_FreeValue(ctx, op1); if (unlikely(ret)) { JS_FreeValue(ctx, res); throw_bf_exception(ctx, ret); return -1; } res = JS_CompactBigInt(ctx, res); *pres = res; return 0; } static int js_unary_arith_bigfloat(JSContext *ctx, JSValue *pres, OPCodeEnum op, JSValue op1) { bf_t a_s, *r, *a; int ret, v; JSValue res; if (op == OP_plus && !is_math_mode(ctx)) { JS_ThrowTypeError(ctx, "bigfloat argument with unary +"); JS_FreeValue(ctx, op1); return -1; } res = JS_NewBigFloat(ctx); if (JS_IsException(res)) { JS_FreeValue(ctx, op1); return -1; } r = JS_GetBigFloat(res); a = JS_ToBigFloat(ctx, &a_s, op1); ret = 0; switch(op) { case OP_inc: case OP_dec: v = 2 * (op - OP_dec) - 1; ret = bf_add_si(r, a, v, ctx->fp_env.prec, ctx->fp_env.flags); break; case OP_plus: ret = bf_set(r, a); break; case OP_neg: ret = bf_set(r, a); bf_neg(r); break; default: abort(); } if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, op1); if (unlikely(ret & BF_ST_MEM_ERROR)) { JS_FreeValue(ctx, res); throw_bf_exception(ctx, ret); return -1; } *pres = res; return 0; } static int js_unary_arith_bigdecimal(JSContext *ctx, JSValue *pres, OPCodeEnum op, JSValue op1) { bfdec_t *r, *a; int ret, v; JSValue res; if (op == OP_plus && !is_math_mode(ctx)) { JS_ThrowTypeError(ctx, "bigdecimal argument with unary +"); JS_FreeValue(ctx, op1); return -1; } res = JS_NewBigDecimal(ctx); if (JS_IsException(res)) { JS_FreeValue(ctx, op1); return -1; } r = JS_GetBigDecimal(res); a = JS_ToBigDecimal(ctx, op1); ret = 0; switch(op) { case OP_inc: case OP_dec: v = 2 * (op - OP_dec) - 1; ret = bfdec_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); break; case OP_plus: ret = bfdec_set(r, a); break; case OP_neg: ret = bfdec_set(r, a); bfdec_neg(r); break; default: abort(); } JS_FreeValue(ctx, op1); if (unlikely(ret)) { JS_FreeValue(ctx, res); throw_bf_exception(ctx, ret); return -1; } *pres = res; return 0; } static no_inline __exception int js_unary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, val; int v, ret; uint32_t tag; op1 = sp[-1]; /* fast path for float64 */ if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) goto handle_float64; if (JS_IsObject(op1)) { ret = js_call_unary_op_fallback(ctx, &val, op1, op); if (ret < 0) return -1; if (ret) { JS_FreeValue(ctx, op1); sp[-1] = val; return 0; } } 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; case JS_TAG_BIG_INT: handle_bigint: if (ctx->rt->bigint_ops.unary_arith(ctx, sp - 1, op, op1)) goto exception; break; case JS_TAG_BIG_FLOAT: if (ctx->rt->bigfloat_ops.unary_arith(ctx, sp - 1, op, op1)) goto exception; break; case JS_TAG_BIG_DECIMAL: if (ctx->rt->bigdecimal_ops.unary_arith(ctx, sp - 1, op, op1)) goto exception; break; default: handle_float64: { double d; if (is_math_mode(ctx)) goto handle_bigint; 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_UNDEFINED; 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_UNDEFINED; 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, val; int ret; op1 = sp[-1]; if (JS_IsObject(op1)) { ret = js_call_unary_op_fallback(ctx, &val, op1, OP_not); if (ret < 0) return -1; if (ret) { JS_FreeValue(ctx, op1); sp[-1] = val; return 0; } } op1 = JS_ToNumericFree(ctx, op1); if (JS_IsException(op1)) goto exception; if (is_math_mode(ctx) || JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { if (ctx->rt->bigint_ops.unary_arith(ctx, sp - 1, OP_not, op1)) goto exception; } else { int32_t v1; if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) goto exception; sp[-1] = JS_NewInt32(ctx, ~v1); } return 0; exception: sp[-1] = JS_UNDEFINED; return -1; } static int js_binary_arith_bigfloat(JSContext *ctx, OPCodeEnum op, JSValue *pres, JSValue op1, JSValue op2) { bf_t a_s, b_s, *r, *a, *b; int ret; JSValue res; res = JS_NewBigFloat(ctx); if (JS_IsException(res)) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return -1; } r = JS_GetBigFloat(res); a = JS_ToBigFloat(ctx, &a_s, op1); b = JS_ToBigFloat(ctx, &b_s, op2); bf_init(ctx->bf_ctx, r); switch(op) { case OP_add: ret = bf_add(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); break; case OP_sub: ret = bf_sub(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); break; case OP_mul: ret = bf_mul(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); break; case OP_div: ret = bf_div(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); break; case OP_math_mod: /* Euclidian remainder */ ret = bf_rem(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags, BF_DIVREM_EUCLIDIAN); break; case OP_mod: ret = bf_rem(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags, BF_RNDZ); break; case OP_pow: ret = bf_pow(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags | BF_POW_JS_QUIRKS); break; default: abort(); } if (a == &a_s) bf_delete(a); if (b == &b_s) bf_delete(b); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (unlikely(ret & BF_ST_MEM_ERROR)) { JS_FreeValue(ctx, res); throw_bf_exception(ctx, ret); return -1; } *pres = res; return 0; } static int js_binary_arith_bigint(JSContext *ctx, OPCodeEnum op, JSValue *pres, JSValue op1, JSValue op2) { bf_t a_s, b_s, *r, *a, *b; int ret; JSValue res; res = JS_NewBigInt(ctx); if (JS_IsException(res)) goto fail; a = JS_ToBigInt(ctx, &a_s, op1); if (!a) goto fail; b = JS_ToBigInt(ctx, &b_s, op2); if (!b) { JS_FreeBigInt(ctx, a, &a_s); goto fail; } r = JS_GetBigInt(res); ret = 0; switch(op) { case OP_add: ret = bf_add(r, a, b, BF_PREC_INF, BF_RNDZ); break; case OP_sub: ret = bf_sub(r, a, b, BF_PREC_INF, BF_RNDZ); break; case OP_mul: ret = bf_mul(r, a, b, BF_PREC_INF, BF_RNDZ); break; case OP_div: if (!is_math_mode(ctx)) { bf_t rem_s, *rem = &rem_s; bf_init(ctx->bf_ctx, rem); ret = bf_divrem(r, rem, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ); bf_delete(rem); } else { goto math_mode_div_pow; } break; case OP_math_mod: /* Euclidian remainder */ ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_DIVREM_EUCLIDIAN) & BF_ST_INVALID_OP; break; case OP_mod: ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ) & BF_ST_INVALID_OP; break; case OP_pow: if (b->sign) { if (!is_math_mode(ctx)) { ret = BF_ST_INVALID_OP; } else { math_mode_div_pow: JS_FreeValue(ctx, res); ret = js_call_binary_op_simple(ctx, &res, ctx->class_proto[JS_CLASS_BIG_INT], op1, op2, op); if (ret != 0) { JS_FreeBigInt(ctx, a, &a_s); JS_FreeBigInt(ctx, b, &b_s); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (ret < 0) { return -1; } else { *pres = res; return 0; } } /* if no BigInt power operator defined, return a bigfloat */ res = JS_NewBigFloat(ctx); if (JS_IsException(res)) { JS_FreeBigInt(ctx, a, &a_s); JS_FreeBigInt(ctx, b, &b_s); goto fail; } r = JS_GetBigFloat(res); if (op == OP_div) { ret = bf_div(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags) & BF_ST_MEM_ERROR; } else { ret = bf_pow(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags | BF_POW_JS_QUIRKS) & BF_ST_MEM_ERROR; } JS_FreeBigInt(ctx, a, &a_s); JS_FreeBigInt(ctx, b, &b_s); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (unlikely(ret)) { JS_FreeValue(ctx, res); throw_bf_exception(ctx, ret); return -1; } *pres = res; return 0; } } else { ret = bf_pow(r, a, b, BF_PREC_INF, BF_RNDZ | BF_POW_JS_QUIRKS); } break; /* logical operations */ case OP_shl: case OP_sar: { slimb_t v2; #if LIMB_BITS == 32 bf_get_int32(&v2, b, 0); if (v2 == INT32_MIN) v2 = INT32_MIN + 1; #else bf_get_int64(&v2, b, 0); if (v2 == INT64_MIN) v2 = INT64_MIN + 1; #endif if (op == OP_sar) v2 = -v2; ret = bf_set(r, a); ret |= bf_mul_2exp(r, v2, BF_PREC_INF, BF_RNDZ); if (v2 < 0) { ret |= bf_rint(r, BF_RNDD) & (BF_ST_OVERFLOW | BF_ST_MEM_ERROR); } } break; case OP_and: ret = bf_logic_and(r, a, b); break; case OP_or: ret = bf_logic_or(r, a, b); break; case OP_xor: ret = bf_logic_xor(r, a, b); break; default: abort(); } JS_FreeBigInt(ctx, a, &a_s); JS_FreeBigInt(ctx, b, &b_s); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (unlikely(ret)) { JS_FreeValue(ctx, res); throw_bf_exception(ctx, ret); return -1; } *pres = JS_CompactBigInt(ctx, res); return 0; fail: JS_FreeValue(ctx, res); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return -1; } /* b must be a positive integer */ static int js_bfdec_pow(bfdec_t *r, const bfdec_t *a, const bfdec_t *b) { bfdec_t b1; int32_t b2; int ret; bfdec_init(b->ctx, &b1); ret = bfdec_set(&b1, b); if (ret) { bfdec_delete(&b1); return ret; } ret = bfdec_rint(&b1, BF_RNDZ); if (ret) { bfdec_delete(&b1); return BF_ST_INVALID_OP; /* must be an integer */ } ret = bfdec_get_int32(&b2, &b1); bfdec_delete(&b1); if (ret) return ret; /* overflow */ if (b2 < 0) return BF_ST_INVALID_OP; /* must be positive */ return bfdec_pow_ui(r, a, b2); } static int js_binary_arith_bigdecimal(JSContext *ctx, OPCodeEnum op, JSValue *pres, JSValue op1, JSValue op2) { bfdec_t *r, *a, *b; int ret; JSValue res; res = JS_NewBigDecimal(ctx); if (JS_IsException(res)) goto fail; r = JS_GetBigDecimal(res); a = JS_ToBigDecimal(ctx, op1); if (!a) goto fail; b = JS_ToBigDecimal(ctx, op2); if (!b) goto fail; switch(op) { case OP_add: ret = bfdec_add(r, a, b, BF_PREC_INF, BF_RNDZ); break; case OP_sub: ret = bfdec_sub(r, a, b, BF_PREC_INF, BF_RNDZ); break; case OP_mul: ret = bfdec_mul(r, a, b, BF_PREC_INF, BF_RNDZ); break; case OP_div: ret = bfdec_div(r, a, b, BF_PREC_INF, BF_RNDZ); break; case OP_math_mod: /* Euclidian remainder */ ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_DIVREM_EUCLIDIAN); break; case OP_mod: ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ); break; case OP_pow: ret = js_bfdec_pow(r, a, b); break; default: abort(); } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (unlikely(ret)) { JS_FreeValue(ctx, res); throw_bf_exception(ctx, ret); return -1; } *pres = res; return 0; fail: JS_FreeValue(ctx, res); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return -1; } static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2, res; uint32_t tag1, tag2; int ret; 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; } /* try to call an overloaded operator */ if ((tag1 == JS_TAG_OBJECT && (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || (tag2 == JS_TAG_OBJECT && (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op, TRUE, 0); if (ret != 0) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (ret < 0) { goto exception; } else { sp[-2] = res; return 0; } } } 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 (is_math_mode(ctx) && (v < -MAX_SAFE_INTEGER || v > MAX_SAFE_INTEGER)) goto handle_bigint; if (v == 0 && (v1 | v2) < 0) { sp[-2] = __JS_NewFloat64(ctx, -0.0); return 0; } break; case OP_div: if (is_math_mode(ctx)) goto handle_bigint; sp[-2] = __JS_NewFloat64(ctx, (double)v1 / (double)v2); return 0; case OP_math_mod: if (unlikely(v2 == 0)) { throw_bf_exception(ctx, BF_ST_DIVIDE_ZERO); goto exception; } v = (int64_t)v1 % (int64_t)v2; if (v < 0) { if (v2 < 0) v -= v2; else v += v2; } break; 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: if (!is_math_mode(ctx)) { sp[-2] = JS_NewFloat64(ctx, js_pow(v1, v2)); return 0; } else { goto handle_bigint; } break; default: abort(); } sp[-2] = JS_NewInt64(ctx, v); } else if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { if (ctx->rt->bigdecimal_ops.binary_arith(ctx, op, sp - 2, op1, op2)) goto exception; } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { if (ctx->rt->bigfloat_ops.binary_arith(ctx, op, sp - 2, op1, op2)) goto exception; } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { handle_bigint: if (ctx->rt->bigint_ops.binary_arith(ctx, op, sp - 2, op1, op2)) goto exception; } else if (tag1 == JS_TAG_FLOAT64 || tag2 == JS_TAG_FLOAT64) { 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: if (is_math_mode(ctx) && is_safe_integer(d1) && is_safe_integer(d2)) goto handle_bigint; 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_math_mod: d2 = fabs(d2); dr = fmod(d1, d2); /* XXX: loss of accuracy if dr < 0 */ if (dr < 0) dr += 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_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) { JSValue op1, op2, res; uint32_t tag1, tag2; int ret; op1 = sp[-2]; op2 = sp[-1]; tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); /* fast path for float64 */ if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { double d1, d2; d1 = JS_VALUE_GET_FLOAT64(op1); d2 = JS_VALUE_GET_FLOAT64(op2); sp[-2] = __JS_NewFloat64(ctx, d1 + d2); return 0; } if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { /* try to call an overloaded operator */ if ((tag1 == JS_TAG_OBJECT && (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED && tag2 != JS_TAG_STRING)) || (tag2 == JS_TAG_OBJECT && (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED && tag1 != JS_TAG_STRING))) { ret = js_call_binary_op_fallback(ctx, &res, op1, op2, OP_add, FALSE, HINT_NONE); if (ret != 0) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (ret < 0) { goto exception; } else { sp[-2] = res; return 0; } } } 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; } tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); } if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { sp[-2] = JS_ConcatString(ctx, op1, op2); if (JS_IsException(sp[-2])) goto exception; return 0; } 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); v = (int64_t)v1 + (int64_t)v2; sp[-2] = JS_NewInt64(ctx, v); } else if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { if (ctx->rt->bigdecimal_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) goto exception; } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { if (ctx->rt->bigfloat_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) goto exception; } else if (tag1 == JS_TAG_FLOAT64 || tag2 == JS_TAG_FLOAT64) { double d1, d2; /* float64 result */ if (JS_ToFloat64Free(ctx, &d1, op1)) { JS_FreeValue(ctx, op2); goto exception; } if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; sp[-2] = __JS_NewFloat64(ctx, d1 + d2); } else { if (ctx->rt->bigint_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) goto exception; } return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline __exception int js_binary_logic_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2, res; int ret; uint32_t tag1, tag2; uint32_t v1, v2, r; op1 = sp[-2]; op2 = sp[-1]; tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); /* try to call an overloaded operator */ if ((tag1 == JS_TAG_OBJECT && (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || (tag2 == JS_TAG_OBJECT && (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op, TRUE, 0); if (ret != 0) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (ret < 0) { goto exception; } else { sp[-2] = res; return 0; } } } 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; } if (is_math_mode(ctx)) goto bigint_op; tag1 = JS_VALUE_GET_TAG(op1); tag2 = JS_VALUE_GET_TAG(op2); if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { if (tag1 != tag2) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); JS_ThrowTypeError(ctx, "both operands must be bigint"); goto exception; } else { bigint_op: if (ctx->rt->bigint_ops.binary_arith(ctx, op, sp - 2, op1, op2)) goto exception; } } else { if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { JS_FreeValue(ctx, op2); goto exception; } if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) goto exception; switch(op) { case OP_shl: r = v1 << (v2 & 0x1f); break; case OP_sar: r = (int)v1 >> (v2 & 0x1f); break; case OP_and: r = v1 & v2; break; case OP_or: r = v1 | v2; break; case OP_xor: r = v1 ^ v2; break; default: abort(); } sp[-2] = JS_NewInt32(ctx, r); } return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } /* Note: also used for bigint */ static int js_compare_bigfloat(JSContext *ctx, OPCodeEnum op, JSValue op1, JSValue op2) { bf_t a_s, b_s, *a, *b; int res; a = JS_ToBigFloat(ctx, &a_s, op1); if (!a) { JS_FreeValue(ctx, op2); return -1; } b = JS_ToBigFloat(ctx, &b_s, op2); if (!b) { if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, op1); return -1; } switch(op) { case OP_lt: res = bf_cmp_lt(a, b); /* if NaN return false */ break; case OP_lte: res = bf_cmp_le(a, b); /* if NaN return false */ break; case OP_gt: res = bf_cmp_lt(b, a); /* if NaN return false */ break; case OP_gte: res = bf_cmp_le(b, a); /* if NaN return false */ break; case OP_eq: res = bf_cmp_eq(a, b); /* if NaN return false */ break; default: abort(); } if (a == &a_s) bf_delete(a); if (b == &b_s) bf_delete(b); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return res; } static int js_compare_bigdecimal(JSContext *ctx, OPCodeEnum op, JSValue op1, JSValue op2) { bfdec_t *a, *b; int res; /* Note: binary floats are converted to bigdecimal with toString(). It is not mathematically correct but is consistent with the BigDecimal() constructor behavior */ op1 = JS_ToBigDecimalFree(ctx, op1, TRUE); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); return -1; } op2 = JS_ToBigDecimalFree(ctx, op2, TRUE); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); return -1; } a = JS_ToBigDecimal(ctx, op1); b = JS_ToBigDecimal(ctx, op2); switch(op) { case OP_lt: res = bfdec_cmp_lt(a, b); /* if NaN return false */ break; case OP_lte: res = bfdec_cmp_le(a, b); /* if NaN return false */ break; case OP_gt: res = bfdec_cmp_lt(b, a); /* if NaN return false */ break; case OP_gte: res = bfdec_cmp_le(b, a); /* if NaN return false */ break; case OP_eq: res = bfdec_cmp_eq(a, b); /* if NaN return false */ break; default: abort(); } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return res; } static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2, ret; int res; uint32_t tag1, tag2; op1 = sp[-2]; op2 = sp[-1]; tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); /* try to call an overloaded operator */ if ((tag1 == JS_TAG_OBJECT && (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || (tag2 == JS_TAG_OBJECT && (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { res = js_call_binary_op_fallback(ctx, &ret, op1, op2, op, FALSE, HINT_NUMBER); if (res != 0) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (res < 0) { goto exception; } else { sp[-2] = ret; return 0; } } } op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); goto exception; } op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); 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_STRING && tag2 == JS_TAG_STRING) { JSString *p1, *p2; p1 = JS_VALUE_GET_STRING(op1); p2 = JS_VALUE_GET_STRING(op2); res = js_string_compare(ctx, p1, p2); switch(op) { case OP_lt: res = (res < 0); break; case OP_lte: res = (res <= 0); break; case OP_gt: res = (res > 0); break; default: case OP_gte: res = (res >= 0); break; } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { /* can use floating point comparison */ 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); } switch(op) { case OP_lt: res = (d1 < d2); /* if NaN return false */ break; case OP_lte: res = (d1 <= d2); /* if NaN return false */ break; case OP_gt: res = (d1 > d2); /* if NaN return false */ break; default: case OP_gte: res = (d1 >= d2); /* if NaN return false */ break; } } else { if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) || (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING)) && !is_math_mode(ctx)) { if (tag1 == JS_TAG_STRING) { op1 = JS_StringToBigInt(ctx, op1); if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) goto invalid_bigint_string; } if (tag2 == JS_TAG_STRING) { op2 = JS_StringToBigInt(ctx, op2); if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { invalid_bigint_string: JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); res = FALSE; goto done; } } } else { 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; } } if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_DECIMAL || JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_DECIMAL) { res = ctx->rt->bigdecimal_ops.compare(ctx, op, op1, op2); if (res < 0) goto exception; } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_FLOAT || JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_FLOAT) { res = ctx->rt->bigfloat_ops.compare(ctx, op, op1, op2); if (res < 0) goto exception; } else { res = ctx->rt->bigint_ops.compare(ctx, op, op1, op2); if (res < 0) goto exception; } } done: sp[-2] = JS_NewBool(ctx, res); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static BOOL tag_is_number(uint32_t tag) { return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT || tag == JS_TAG_FLOAT64 || tag == JS_TAG_BIG_FLOAT || tag == JS_TAG_BIG_DECIMAL); } static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq) { JSValue op1, op2, ret; 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 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { res = ctx->rt->bigdecimal_ops.compare(ctx, OP_eq, op1, op2); if (res < 0) goto exception; } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { res = ctx->rt->bigfloat_ops.compare(ctx, OP_eq, op1, op2); if (res < 0) goto exception; } else { res = ctx->rt->bigint_ops.compare(ctx, OP_eq, op1, op2); if (res < 0) goto exception; } } else if (tag1 == tag2) { if (tag1 == JS_TAG_OBJECT) { /* try the fallback operator */ res = js_call_binary_op_fallback(ctx, &ret, op1, op2, is_neq ? OP_neq : OP_eq, FALSE, HINT_NONE); if (res != 0) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (res < 0) { goto exception; } else { sp[-2] = ret; return 0; } } } res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { res = TRUE; } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) || (tag2 == JS_TAG_STRING && tag_is_number(tag1))) { if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) && !is_math_mode(ctx)) { if (tag1 == JS_TAG_STRING) { op1 = JS_StringToBigInt(ctx, op1); if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) goto invalid_bigint_string; } if (tag2 == JS_TAG_STRING) { op2 = JS_StringToBigInt(ctx, op2); if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { invalid_bigint_string: JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); res = FALSE; goto done; } } } else { 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_eq(ctx, op1, op2); } 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) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) || (tag2 == JS_TAG_OBJECT && (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) { /* try the fallback operator */ res = js_call_binary_op_fallback(ctx, &ret, op1, op2, is_neq ? OP_neq : OP_eq, FALSE, HINT_NONE); if (res != 0) { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); if (res < 0) { goto exception; } else { sp[-2] = ret; return 0; } } 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 { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); res = FALSE; } done: sp[-2] = JS_NewBool(ctx, res ^ is_neq); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) { JSValue op1, op2; uint32_t v1, v2, r; op1 = sp[-2]; op2 = sp[-1]; 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; } /* XXX: could forbid >>> in bignum mode */ if (!is_math_mode(ctx) && (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT)) { JS_ThrowTypeError(ctx, "bigint operands are forbidden for >>>"); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); goto exception; } /* cannot give an exception */ JS_ToUint32Free(ctx, &v1, op1); JS_ToUint32Free(ctx, &v2, op2); r = v1 >> (v2 & 0x1f); sp[-2] = JS_NewUint32(ctx, r); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static JSValue js_mul_pow10_to_float64(JSContext *ctx, const bf_t *a, int64_t exponent) { bf_t r_s, *r = &r_s; double d; int ret; /* always convert to Float64 */ bf_init(ctx->bf_ctx, r); ret = bf_mul_pow_radix(r, a, 10, exponent, 53, bf_set_exp_bits(11) | BF_RNDN | BF_FLAG_SUBNORMAL); bf_get_float64(r, &d, BF_RNDN); bf_delete(r); if (ret & BF_ST_MEM_ERROR) return JS_ThrowOutOfMemory(ctx); else return __JS_NewFloat64(ctx, d); } static no_inline int js_mul_pow10(JSContext *ctx, JSValue *sp) { bf_t a_s, *a, *r; JSValue op1, op2, res; int64_t e; int ret; res = JS_NewBigFloat(ctx); if (JS_IsException(res)) return -1; r = JS_GetBigFloat(res); op1 = sp[-2]; op2 = sp[-1]; a = JS_ToBigFloat(ctx, &a_s, op1); if (!a) return -1; if (JS_IsBigInt(ctx, op2)) { ret = JS_ToBigInt64(ctx, &e, op2); } else { ret = JS_ToInt64(ctx, &e, op2); } if (ret) { if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, res); return -1; } bf_mul_pow_radix(r, a, 10, e, ctx->fp_env.prec, ctx->fp_env.flags); if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); sp[-2] = res; return 0; } #else /* !CONFIG_BIGNUM */ static no_inline __exception int js_unary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1; double d; op1 = sp[-1]; if (unlikely(JS_ToFloat64Free(ctx, &d, op1))) { sp[-1] = JS_UNDEFINED; return -1; } switch(op) { case OP_inc: d++; break; case OP_dec: d--; break; case OP_plus: break; case OP_neg: d = -d; break; default: abort(); } sp[-1] = JS_NewFloat64(ctx, d); return 0; } /* specific case necessary for correct return value semantics */ static __exception int js_post_inc_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1; double d, r; op1 = sp[-1]; if (unlikely(JS_ToFloat64Free(ctx, &d, op1))) { sp[-1] = JS_UNDEFINED; return -1; } r = d + 2 * (op - OP_post_dec) - 1; sp[0] = JS_NewFloat64(ctx, r); sp[-1] = JS_NewFloat64(ctx, d); return 0; } static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2; double d1, d2, r; op1 = sp[-2]; op2 = sp[-1]; if (unlikely(JS_ToFloat64Free(ctx, &d1, op1))) { JS_FreeValue(ctx, op2); goto exception; } if (unlikely(JS_ToFloat64Free(ctx, &d2, op2))) { goto exception; } switch(op) { case OP_sub: r = d1 - d2; break; case OP_mul: r = d1 * d2; break; case OP_div: r = d1 / d2; break; case OP_mod: r = fmod(d1, d2); break; case OP_pow: r = js_pow(d1, d2); break; default: abort(); } sp[-2] = JS_NewFloat64(ctx, r); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) { JSValue op1, op2; uint32_t tag1, tag2; op1 = sp[-2]; op2 = sp[-1]; tag1 = JS_VALUE_GET_TAG(op1); tag2 = JS_VALUE_GET_TAG(op2); if ((tag1 == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag1)) && (tag2 == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag2))) { goto add_numbers; } else { 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; } tag1 = JS_VALUE_GET_TAG(op1); tag2 = JS_VALUE_GET_TAG(op2); if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { sp[-2] = JS_ConcatString(ctx, op1, op2); if (JS_IsException(sp[-2])) goto exception; } else { double d1, d2; add_numbers: if (JS_ToFloat64Free(ctx, &d1, op1)) { JS_FreeValue(ctx, op2); goto exception; } if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; sp[-2] = JS_NewFloat64(ctx, d1 + d2); } } return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline __exception int js_binary_logic_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2; uint32_t v1, v2, r; op1 = sp[-2]; op2 = sp[-1]; if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { JS_FreeValue(ctx, op2); goto exception; } if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) goto exception; switch(op) { case OP_shl: r = v1 << (v2 & 0x1f); break; case OP_sar: r = (int)v1 >> (v2 & 0x1f); break; case OP_and: r = v1 & v2; break; case OP_or: r = v1 | v2; break; case OP_xor: r = v1 ^ v2; break; default: abort(); } sp[-2] = JS_NewInt32(ctx, r); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) { int32_t v1; if (unlikely(JS_ToInt32Free(ctx, &v1, sp[-1]))) { sp[-1] = JS_UNDEFINED; return -1; } sp[-1] = JS_NewInt32(ctx, ~v1); return 0; } static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2; int res; op1 = sp[-2]; op2 = sp[-1]; op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); goto exception; } op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); goto exception; } if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING && JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { JSString *p1, *p2; p1 = JS_VALUE_GET_STRING(op1); p2 = JS_VALUE_GET_STRING(op2); res = js_string_compare(ctx, p1, p2); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); switch(op) { case OP_lt: res = (res < 0); break; case OP_lte: res = (res <= 0); break; case OP_gt: res = (res > 0); break; default: case OP_gte: res = (res >= 0); break; } } else { double d1, d2; if (JS_ToFloat64Free(ctx, &d1, op1)) { JS_FreeValue(ctx, op2); goto exception; } if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; switch(op) { case OP_lt: res = (d1 < d2); /* if NaN return false */ break; case OP_lte: res = (d1 <= d2); /* if NaN return false */ break; case OP_gt: res = (d1 > d2); /* if NaN return false */ break; default: case OP_gte: res = (d1 >= d2); /* if NaN return false */ break; } } sp[-2] = JS_NewBool(ctx, res); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq) { JSValue op1, op2; int tag1, tag2; BOOL res; op1 = sp[-2]; op2 = sp[-1]; redo: tag1 = JS_VALUE_GET_NORM_TAG(op1); tag2 = JS_VALUE_GET_NORM_TAG(op2); if (tag1 == tag2 || (tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || (tag2 == JS_TAG_INT && tag1 == JS_TAG_FLOAT64)) { res = js_strict_eq(ctx, op1, op2); } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { res = TRUE; } else if ((tag1 == JS_TAG_STRING && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || (tag2 == JS_TAG_STRING && (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { double d1; double d2; if (JS_ToFloat64Free(ctx, &d1, op1)) { JS_FreeValue(ctx, op2); goto exception; } if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; res = (d1 == d2); } 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 && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64 || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) { op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); if (JS_IsException(op1)) { JS_FreeValue(ctx, op2); goto exception; } goto redo; } else if (tag2 == JS_TAG_OBJECT && (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64 || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL)) { op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); goto exception; } goto redo; } else { JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); res = FALSE; } sp[-2] = JS_NewBool(ctx, res ^ is_neq); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) { JSValue op1, op2; uint32_t v1, v2, r; op1 = sp[-2]; op2 = sp[-1]; if (unlikely(JS_ToUint32Free(ctx, &v1, op1))) { JS_FreeValue(ctx, op2); goto exception; } if (unlikely(JS_ToUint32Free(ctx, &v2, op2))) goto exception; r = v1 >> (v2 & 0x1f); sp[-2] = JS_NewUint32(ctx, r); return 0; exception: sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } #endif /* !CONFIG_BIGNUM */ /* 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: case JS_TAG_UNDEFINED: res = (tag1 == tag2); break; case JS_TAG_STRING: { JSString *p1, *p2; if (tag1 != tag2) { res = FALSE; } else { p1 = JS_VALUE_GET_STRING(op1); p2 = JS_VALUE_GET_STRING(op2); res = (js_string_compare(ctx, p1, p2) == 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; #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: { bf_t a_s, *a, b_s, *b; if (tag1 != tag2) { res = FALSE; break; } a = JS_ToBigFloat(ctx, &a_s, op1); b = JS_ToBigFloat(ctx, &b_s, op2); res = bf_cmp_eq(a, b); if (a == &a_s) bf_delete(a); if (b == &b_s) bf_delete(b); } break; case JS_TAG_BIG_FLOAT: { JSBigFloat *p1, *p2; const bf_t *a, *b; if (tag1 != tag2) { res = FALSE; break; } p1 = JS_VALUE_GET_PTR(op1); p2 = JS_VALUE_GET_PTR(op2); a = &p1->num; b = &p2->num; if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { if (eq_mode == JS_EQ_SAME_VALUE_ZERO && a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) { res = TRUE; } else { res = (bf_cmp_full(a, b) == 0); } } else { res = bf_cmp_eq(a, b); } } break; case JS_TAG_BIG_DECIMAL: { JSBigDecimal *p1, *p2; const bfdec_t *a, *b; if (tag1 != tag2) { res = FALSE; break; } p1 = JS_VALUE_GET_PTR(op1); p2 = JS_VALUE_GET_PTR(op2); a = &p1->num; b = &p2->num; res = bfdec_cmp_eq(a, b); } break; #endif default: res = FALSE; break; } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); done_no_free: return res; } static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) { return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } 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); } 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); } static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, BOOL is_neq) { BOOL res; res = js_strict_eq(ctx, sp[-2], sp[-1]); 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_has_unscopable(JSContext *ctx, JSValueConst obj, JSAtom atom) { JSValue arr, val; int ret; arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); if (JS_IsException(arr)) return -1; ret = 0; if (JS_IsObject(arr)) { val = JS_GetProperty(ctx, arr, atom); ret = JS_ToBoolFree(ctx, val); } JS_FreeValue(ctx, arr); return ret; } static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) { JSValue op1, op2; BOOL ret; op1 = sp[-2]; op2 = sp[-1]; ret = JS_IsInstanceOf(ctx, op1, op2); if (ret < 0) return ret; JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); sp[-2] = JS_NewBool(ctx, ret); return 0; } static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) { JSAtom atom; uint32_t tag; tag = JS_VALUE_GET_NORM_TAG(op1); switch(tag) { #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: atom = JS_ATOM_bigint; break; case JS_TAG_BIG_FLOAT: atom = JS_ATOM_bigfloat; break; case JS_TAG_BIG_DECIMAL: atom = JS_ATOM_bigdecimal; break; #endif case JS_TAG_INT: case JS_TAG_FLOAT64: atom = JS_ATOM_number; break; case JS_TAG_UNDEFINED: atom = JS_ATOM_undefined; break; case JS_TAG_BOOL: atom = JS_ATOM_boolean; break; case JS_TAG_STRING: atom = JS_ATOM_string; break; case JS_TAG_OBJECT: if (JS_IsFunction(ctx, op1)) atom = JS_ATOM_function; else goto obj_type; break; case JS_TAG_NULL: obj_type: atom = JS_ATOM_object; break; case JS_TAG_SYMBOL: atom = JS_ATOM_symbol; break; default: atom = JS_ATOM_unknown; break; } return atom; } 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; } static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ThrowTypeError(ctx, "invalid property access"); } /* 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_function_proto_caller(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); if (!b || (b->js_mode & JS_MODE_STRICT) || !b->has_prototype) { return js_throw_type_error(ctx, this_val, 0, NULL); } return JS_UNDEFINED; } static JSValue js_function_proto_fileName(JSContext *ctx, JSValueConst this_val) { JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); if (b && b->has_debug) { return JS_AtomToString(ctx, b->debug.filename); } return JS_UNDEFINED; } static JSValue js_function_proto_lineNumber(JSContext *ctx, JSValueConst this_val) { JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); if (b && b->has_debug) { return JS_NewInt32(ctx, b->debug.line_num); } return JS_UNDEFINED; } static int js_arguments_define_own_property(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { JSObject *p; uint32_t idx; p = JS_VALUE_GET_OBJ(this_obj); /* convert to normal array when redefining an existing numeric field */ if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && idx < p->u.array.count) { if (convert_fast_array_to_array(ctx, p)) return -1; } /* run the default define own property */ return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, flags | JS_PROP_NO_EXOTIC); } static const JSClassExoticMethods js_arguments_exotic_methods = { .define_own_property = js_arguments_define_own_property, }; static JSValue js_build_arguments(JSContext *ctx, int argc, JSValueConst *argv) { JSValue val, *tab; JSProperty *pr; JSObject *p; int i; val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_ARGUMENTS); if (JS_IsException(val)) return val; p = JS_VALUE_GET_OBJ(val); /* add the length field (cannot fail) */ pr = add_property(ctx, p, JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); pr->u.value = JS_NewInt32(ctx, argc); /* initialize the fast array part */ tab = NULL; if (argc > 0) { tab = js_malloc(ctx, sizeof(tab[0]) * argc); if (!tab) { JS_FreeValue(ctx, val); return JS_EXCEPTION; } for(i = 0; i < argc; i++) { tab[i] = JS_DupValue(ctx, argv[i]); } } p->u.array.u.values = tab; p->u.array.count = argc; JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, JS_DupValue(ctx, ctx->array_proto_values), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); /* add callee property to throw a TypeError in strict mode */ JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED, ctx->throw_type_error, ctx->throw_type_error, JS_PROP_HAS_GET | JS_PROP_HAS_SET); return val; } #define GLOBAL_VAR_OFFSET 0x40000000 #define ARGUMENT_VAR_OFFSET 0x20000000 /* legacy arguments object: add references to the function arguments */ static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, JSValueConst *argv, JSStackFrame *sf, int arg_count) { JSValue val; JSProperty *pr; JSObject *p; int i; val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_MAPPED_ARGUMENTS); if (JS_IsException(val)) return val; p = JS_VALUE_GET_OBJ(val); /* add the length field (cannot fail) */ pr = add_property(ctx, p, JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); pr->u.value = JS_NewInt32(ctx, argc); for(i = 0; i < arg_count; i++) { JSVarRef *var_ref; var_ref = get_var_ref(ctx, sf, i, TRUE); if (!var_ref) goto fail; pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); if (!pr) { free_var_ref(ctx->rt, var_ref); goto fail; } pr->u.var_ref = var_ref; } /* the arguments not mapped to the arguments of the function can be normal properties */ for(i = arg_count; i < argc; i++) { if (JS_DefinePropertyValueUint32(ctx, val, i, JS_DupValue(ctx, argv[i]), JS_PROP_C_W_E) < 0) goto fail; } JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, JS_DupValue(ctx, ctx->array_proto_values), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); /* callee returns this function in non strict mode */ JS_DefinePropertyValue(ctx, val, JS_ATOM_callee, JS_DupValue(ctx, ctx->current_stack_frame->cur_func), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); return val; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } 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 JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) { JSObject *p, *p1; JSPropertyEnum *tab_atom; int i; JSValue enum_obj; JSForInIterator *it; uint32_t tag, tab_atom_count; tag = JS_VALUE_GET_TAG(obj); if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { obj = JS_ToObjectFree(ctx, obj); } it = js_malloc(ctx, sizeof(*it)); if (!it) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); if (JS_IsException(enum_obj)) { js_free(ctx, it); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } it->is_array = FALSE; it->obj = obj; it->idx = 0; p = JS_VALUE_GET_OBJ(enum_obj); p->u.for_in_iterator = it; if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) return enum_obj; p = JS_VALUE_GET_OBJ(obj); /* fast path: assume no enumerable properties in the prototype chain */ p1 = p->shape->proto; while (p1 != NULL) { if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p1, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) goto fail; js_free_prop_enum(ctx, tab_atom, tab_atom_count); if (tab_atom_count != 0) { goto slow_path; } p1 = p1->shape->proto; } if (p->fast_array) { JSShape *sh; JSShapeProperty *prs; /* check that there are no enumerable normal fields */ sh = p->shape; for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { if (prs->flags & JS_PROP_ENUMERABLE) goto normal_case; } /* the implicit GetOwnProperty raises an exception if the typed array is detached */ if ((p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) && typed_array_is_detached(ctx, p) && typed_array_get_length(ctx, p) != 0) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } /* for fast arrays, we only store the number of elements */ it->is_array = TRUE; it->array_length = p->u.array.count; } else { normal_case: if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) goto fail; for(i = 0; i < tab_atom_count; i++) { JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0); } js_free_prop_enum(ctx, tab_atom, tab_atom_count); } return enum_obj; slow_path: /* non enumerable properties hide the enumerables ones in the prototype chain */ while (p != NULL) { if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) goto fail; for(i = 0; i < tab_atom_count; i++) { JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL, (tab_atom[i].is_enumerable ? JS_PROP_ENUMERABLE : 0)); } js_free_prop_enum(ctx, tab_atom, tab_atom_count); p = p->shape->proto; } return enum_obj; fail: JS_FreeValue(ctx, enum_obj); return JS_EXCEPTION; } /* obj -> enum_obj */ static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) { sp[-1] = build_for_in_iterator(ctx, sp[-1]); if (JS_IsException(sp[-1])) return -1; return 0; } /* enum_obj -> enum_obj value done */ static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) { JSValueConst enum_obj; JSObject *p; JSAtom prop; JSForInIterator *it; int ret; enum_obj = sp[-1]; /* fail safe */ if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) goto done; p = JS_VALUE_GET_OBJ(enum_obj); if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) goto done; it = p->u.for_in_iterator; for(;;) { if (it->is_array) { if (it->idx >= it->array_length) goto done; prop = __JS_AtomFromUInt32(it->idx); it->idx++; } else { JSShape *sh = p->shape; JSShapeProperty *prs; if (it->idx >= sh->prop_count) goto done; prs = get_shape_prop(sh) + it->idx; prop = prs->atom; it->idx++; if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) continue; } /* check if the property was deleted */ ret = JS_HasProperty(ctx, it->obj, prop); if (ret < 0) return ret; if (ret) break; } /* return the property */ sp[0] = JS_AtomToValue(ctx, prop); sp[1] = JS_FALSE; return 0; done: /* return the end */ sp[0] = JS_UNDEFINED; sp[1] = JS_TRUE; return 0; } static JSValue JS_GetIterator2(JSContext *ctx, JSValueConst obj, JSValueConst method) { JSValue enum_obj; enum_obj = JS_Call(ctx, method, obj, 0, NULL); if (JS_IsException(enum_obj)) return enum_obj; if (!JS_IsObject(enum_obj)) { JS_FreeValue(ctx, enum_obj); return JS_ThrowTypeErrorNotAnObject(ctx); } return enum_obj; } static JSValue JS_GetIterator(JSContext *ctx, JSValueConst obj, BOOL is_async) { JSValue method, ret, sync_iter; if (is_async) { method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); if (JS_IsException(method)) return method; if (JS_IsUndefined(method) || JS_IsNull(method)) { method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); if (JS_IsException(method)) return method; sync_iter = JS_GetIterator2(ctx, obj, method); JS_FreeValue(ctx, method); if (JS_IsException(sync_iter)) return sync_iter; ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); JS_FreeValue(ctx, sync_iter); return ret; } } else { method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); if (JS_IsException(method)) return method; } if (!JS_IsFunction(ctx, method)) { JS_FreeValue(ctx, method); return JS_ThrowTypeError(ctx, "value is not iterable"); } ret = JS_GetIterator2(ctx, obj, method); JS_FreeValue(ctx, method); return ret; } /* return *pdone = 2 if the iterator object is not parsed */ static JSValue JS_IteratorNext2(JSContext *ctx, JSValueConst enum_obj, JSValueConst method, int argc, JSValueConst *argv, int *pdone) { JSValue obj; /* fast path for the built-in iterators (avoid creating the intermediate result object) */ if (JS_IsObject(method)) { JSObject *p = JS_VALUE_GET_OBJ(method); if (p->class_id == JS_CLASS_C_FUNCTION && p->u.cfunc.cproto == JS_CFUNC_iterator_next) { JSCFunctionType func; JSValueConst args[1]; /* in case the function expects one argument */ if (argc == 0) { args[0] = JS_UNDEFINED; argv = args; } func = p->u.cfunc.c_function; return func.iterator_next(ctx, enum_obj, argc, argv, pdone, p->u.cfunc.magic); } } obj = JS_Call(ctx, method, enum_obj, argc, argv); if (JS_IsException(obj)) goto fail; if (!JS_IsObject(obj)) { JS_FreeValue(ctx, obj); JS_ThrowTypeError(ctx, "iterator must return an object"); goto fail; } *pdone = 2; return obj; fail: *pdone = FALSE; return JS_EXCEPTION; } static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj, JSValueConst method, int argc, JSValueConst *argv, BOOL *pdone) { JSValue obj, value, done_val; int done; obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); if (JS_IsException(obj)) goto fail; if (done != 2) { *pdone = done; return obj; } else { done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); if (JS_IsException(done_val)) goto fail; *pdone = JS_ToBoolFree(ctx, done_val); value = JS_UNDEFINED; if (!*pdone) { value = JS_GetProperty(ctx, obj, JS_ATOM_value); } JS_FreeValue(ctx, obj); return value; } fail: JS_FreeValue(ctx, obj); *pdone = FALSE; return JS_EXCEPTION; } /* return < 0 in case of exception */ static int JS_IteratorClose(JSContext *ctx, JSValueConst enum_obj, BOOL is_exception_pending) { JSValue method, ret, ex_obj; int res; if (is_exception_pending) { ex_obj = ctx->current_exception; ctx->current_exception = JS_NULL; res = -1; } else { ex_obj = JS_UNDEFINED; res = 0; } method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); if (JS_IsException(method)) { res = -1; goto done; } if (JS_IsUndefined(method) || JS_IsNull(method)) { goto done; } ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); if (!is_exception_pending) { if (JS_IsException(ret)) { res = -1; } else if (!JS_IsObject(ret)) { JS_ThrowTypeErrorNotAnObject(ctx); res = -1; } } JS_FreeValue(ctx, ret); done: if (is_exception_pending) { JS_Throw(ctx, ex_obj); } return res; } /* obj -> enum_rec (3 slots) */ static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, BOOL is_async) { JSValue op1, obj, method; op1 = sp[-1]; obj = JS_GetIterator(ctx, op1, is_async); if (JS_IsException(obj)) return -1; JS_FreeValue(ctx, op1); sp[-1] = obj; method = JS_GetProperty(ctx, obj, JS_ATOM_next); if (JS_IsException(method)) return -1; sp[0] = method; return 0; } /* enum_rec -> enum_rec value done */ static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) { JSValue value = JS_UNDEFINED; int done = 1; if (likely(!JS_IsUndefined(sp[offset]))) { value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); if (JS_IsException(value)) done = -1; if (done) { /* value is JS_UNDEFINED or JS_EXCEPTION */ /* replace the iteration object with undefined */ JS_FreeValue(ctx, sp[offset]); sp[offset] = JS_UNDEFINED; if (done < 0) return -1; } } sp[0] = value; sp[1] = JS_NewBool(ctx, done); return 0; } static __exception int js_for_await_of_next(JSContext *ctx, JSValue *sp) { JSValue result; result = JS_Call(ctx, sp[-2], sp[-3], 0, NULL); if (JS_IsException(result)) return -1; sp[0] = result; return 0; } static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValueConst obj, BOOL *pdone) { JSValue done_val, value; BOOL done; done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); if (JS_IsException(done_val)) goto fail; done = JS_ToBoolFree(ctx, done_val); value = JS_GetProperty(ctx, obj, JS_ATOM_value); if (JS_IsException(value)) goto fail; *pdone = done; return value; fail: *pdone = FALSE; return JS_EXCEPTION; } static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) { JSValue obj, value; BOOL done; obj = sp[-1]; if (!JS_IsObject(obj)) { JS_ThrowTypeError(ctx, "iterator must return an object"); return -1; } value = JS_IteratorGetCompleteValue(ctx, obj, &done); if (JS_IsException(value)) return -1; JS_FreeValue(ctx, obj); sp[-1] = value; sp[0] = JS_NewBool(ctx, done); return 0; } static JSValue js_create_iterator_result(JSContext *ctx, JSValue val, BOOL done) { JSValue obj; obj = JS_NewObject(ctx); if (JS_IsException(obj)) { JS_FreeValue(ctx, val); return obj; } if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, val, JS_PROP_C_W_E) < 0) { goto fail; } if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, JS_NewBool(ctx, done), JS_PROP_C_W_E) < 0) { fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } return obj; } static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, BOOL *pdone, int magic); static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); 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_append_enumerate(JSContext *ctx, JSValue *sp) { JSValue iterator, enumobj, method, value; int pos, is_array_iterator; JSValue *arrp; uint32_t i, count32; if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { JS_ThrowInternalError(ctx, "invalid index for append"); return -1; } pos = JS_VALUE_GET_INT(sp[-2]); /* XXX: further optimisations: - use ctx->array_proto_values? - check if array_iterator_prototype next method is built-in and avoid constructing actual iterator object? - build this into js_for_of_start and use in all `for (x of o)` loops */ iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); if (JS_IsException(iterator)) return -1; is_array_iterator = JS_IsCFunction(ctx, iterator, (JSCFunction *)js_create_array_iterator, JS_ITERATOR_KIND_VALUE); JS_FreeValue(ctx, iterator); enumobj = JS_GetIterator(ctx, sp[-1], FALSE); if (JS_IsException(enumobj)) return -1; method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); if (JS_IsException(method)) { JS_FreeValue(ctx, enumobj); return -1; } if (is_array_iterator && JS_IsCFunction(ctx, method, (JSCFunction *)js_array_iterator_next, 0) && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { int64_t len; /* Handle fast arrays explicitly */ if (js_get_length64(ctx, &len, sp[-1])) goto exception; for (i = 0; i < count32; i++) { if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, JS_DupValue(ctx, arrp[i]), JS_PROP_C_W_E) < 0) goto exception; } if (len > count32) { /* This is not strictly correct because the trailing elements are empty instead of undefined. Append undefined entries instead. */ pos += len - count32; if (JS_SetProperty(ctx, sp[-3], JS_ATOM_length, JS_NewUint32(ctx, pos)) < 0) goto exception; } } else { for (;;) { BOOL done; value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); if (JS_IsException(value)) goto exception; if (done) { /* value is JS_UNDEFINED */ break; } if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) goto exception; } } sp[-2] = JS_NewInt32(ctx, pos); JS_FreeValue(ctx, enumobj); JS_FreeValue(ctx, method); return 0; exception: JS_IteratorClose(ctx, enumobj, TRUE); JS_FreeValue(ctx, enumobj); JS_FreeValue(ctx, method); return -1; } 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 = 0, flags; 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); if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY)) return -1; flags = JS_PROP_C_W_E; 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) break; ret = 0; continue; } } ret = -1; val = JS_GetProperty(ctx, source, tab_atom[i].atom); if (JS_IsException(val)) break; if (setprop) ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); else ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, flags); if (ret < 0) break; ret = 0; } js_free_prop_enum(ctx, tab_atom, tab_atom_count); return ret; } /* only valid inside C functions */ static JSValueConst JS_GetActiveFunction(JSContext *ctx) { return ctx->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; list_for_each(el, &sf->var_ref_list) { var_ref = list_entry(el, JSVarRef, header.link); if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) { 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; var_ref->is_detached = FALSE; var_ref->is_arg = is_arg; var_ref->var_idx = var_idx; list_add_tail(&var_ref->header.link, &sf->var_ref_list); if (is_arg) var_ref->pvalue = &sf->arg_buf[var_idx]; else var_ref->pvalue = &sf->var_buf[var_idx]; var_ref->value = JS_UNDEFINED; 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.home_object = NULL; 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 int 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 -1; 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 (JS_DefinePropertyValue(ctx, this_val, atom, obj, JS_PROP_WRITABLE) < 0 || ret < 0) return -1; return 0; } static JSValue js_closure(JSContext *ctx, JSValue bfunc, JSVarRef **cur_var_refs, JSStackFrame *sf) { JSFunctionBytecode *b; JSValue func_obj; JSAtom name_atom; static const uint16_t func_kind_to_class_id[] = { [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, }; b = JS_VALUE_GET_PTR(bfunc); func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); 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); if (b->func_kind & JS_FUNC_GENERATOR) { JSValue proto; int proto_class_id; /* generators have a prototype field which is used as prototype for the generator object */ if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) proto_class_id = JS_CLASS_ASYNC_GENERATOR; else proto_class_id = JS_CLASS_GENERATOR; proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); if (JS_IsException(proto)) goto fail; JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, JS_PROP_WRITABLE); } else if (b->has_prototype) { /* add the 'prototype' property: delay instantiation to avoid creating cycles for every javascript function. The prototype object is created on the fly when first accessed */ JS_SetConstructorBit(ctx, func_obj, TRUE); JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, js_instantiate_prototype, NULL, JS_PROP_WRITABLE); } return func_obj; fail: /* bfunc is freed when func_obj is freed */ JS_FreeValue(ctx, func_obj); return JS_EXCEPTION; } #define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) static int js_op_define_class(JSContext *ctx, JSValue *sp, JSAtom class_name, int class_flags, JSVarRef **cur_var_refs, JSStackFrame *sf, BOOL is_computed_name) { JSValue bfunc, parent_class, proto = JS_UNDEFINED; JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; JSFunctionBytecode *b; parent_class = sp[-2]; bfunc = sp[-1]; if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { if (JS_IsNull(parent_class)) { parent_proto = JS_NULL; parent_class = JS_DupValue(ctx, ctx->function_proto); } else { if (!JS_IsConstructor(ctx, parent_class)) { JS_ThrowTypeError(ctx, "parent class must be constructor"); goto fail; } parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); if (JS_IsException(parent_proto)) goto fail; if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); goto fail; } } } else { /* parent_class is JS_UNDEFINED in this case */ parent_proto = JS_DupValue(ctx, ctx->class_proto[JS_CLASS_OBJECT]); parent_class = JS_DupValue(ctx, ctx->function_proto); } proto = JS_NewObjectProto(ctx, parent_proto); if (JS_IsException(proto)) goto fail; b = JS_VALUE_GET_PTR(bfunc); assert(b->func_kind == JS_FUNC_NORMAL); ctor = JS_NewObjectProtoClass(ctx, parent_class, JS_CLASS_BYTECODE_FUNCTION); if (JS_IsException(ctor)) goto fail; ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf); bfunc = JS_UNDEFINED; if (JS_IsException(ctor)) goto fail; js_method_set_home_object(ctx, ctor, proto); JS_SetConstructorBit(ctx, ctor, TRUE); JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, JS_NewInt32(ctx, b->defined_arg_count), JS_PROP_CONFIGURABLE); if (is_computed_name) { if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], JS_PROP_CONFIGURABLE) < 0) goto fail; } else { if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) goto fail; } /* the constructor property must be first. It can be overriden by computed property names */ if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, JS_DupValue(ctx, ctor), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_THROW) < 0) goto fail; /* set the prototype property */ if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, JS_DupValue(ctx, proto), JS_PROP_THROW) < 0) goto fail; set_cycle_flag(ctx, ctor); set_cycle_flag(ctx, proto); JS_FreeValue(ctx, parent_proto); JS_FreeValue(ctx, parent_class); sp[-2] = ctor; sp[-1] = proto; return 0; fail: JS_FreeValue(ctx, parent_class); JS_FreeValue(ctx, parent_proto); JS_FreeValue(ctx, bfunc); JS_FreeValue(ctx, proto); JS_FreeValue(ctx, ctor); sp[-2] = JS_UNDEFINED; sp[-1] = JS_UNDEFINED; return -1; } static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) { struct list_head *el, *el1; JSVarRef *var_ref; int var_idx; list_for_each_safe(el, el1, &sf->var_ref_list) { var_ref = list_entry(el, JSVarRef, header.link); var_idx = var_ref->var_idx; if (var_ref->is_arg) var_ref->value = JS_DupValueRT(rt, sf->arg_buf[var_idx]); else var_ref->value = JS_DupValueRT(rt, sf->var_buf[var_idx]); var_ref->pvalue = &var_ref->value; /* the reference is no longer to a local variable */ var_ref->is_detached = TRUE; add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); } } static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int idx, int is_arg) { struct list_head *el, *el1; JSVarRef *var_ref; int var_idx = idx; list_for_each_safe(el, el1, &sf->var_ref_list) { var_ref = list_entry(el, JSVarRef, header.link); if (var_idx == var_ref->var_idx && var_ref->is_arg == is_arg) { var_ref->value = JS_DupValue(ctx, sf->var_buf[var_idx]); var_ref->pvalue = &var_ref->value; list_del(&var_ref->header.link); /* the reference is no longer to a local variable */ var_ref->is_detached = TRUE; add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); } } } #define JS_CALL_FLAG_COPY_ARGV (1 << 1) #define JS_CALL_FLAG_GENERATOR (1 << 2) static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { 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(ctx, sizeof(arg_buf[0]) * arg_count)) return JS_ThrowStackOverflow(ctx); prev_sf = ctx->current_stack_frame; sf->prev_frame = prev_sf; ctx->current_stack_frame = sf; #ifdef CONFIG_BIGNUM /* we only propagate the bignum mode as some runtime functions test it */ if (prev_sf) sf->js_mode = prev_sf->js_mode & JS_MODE_MATH; else sf->js_mode = 0; #else sf->js_mode = 0; #endif 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_UNDEFINED; sf->arg_count = arg_count; } sf->arg_buf = (JSValue*)arg_buf; func = p->u.cfunc.c_function; switch(cproto) { case JS_CFUNC_constructor: case JS_CFUNC_constructor_or_func: if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { if (cproto == JS_CFUNC_constructor) { not_a_constructor: ret_val = JS_ThrowTypeError(ctx, "must be called with new"); break; } else { this_obj = JS_UNDEFINED; } } /* here this_obj is new_target */ /* 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: if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { if (cproto == JS_CFUNC_constructor_magic) { goto not_a_constructor; } else { this_obj = JS_UNDEFINED; } } /* 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; case JS_CFUNC_iterator_next: { int done; ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, &done, p->u.cfunc.magic); if (!JS_IsException(ret_val) && done != 2) { ret_val = js_create_iterator_result(ctx, ret_val, done); } } break; default: abort(); } ctx->current_stack_frame = sf->prev_frame; 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, 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]; } if (flags & JS_CALL_FLAG_CONSTRUCTOR) { new_target = this_obj; if (js_same_value(ctx, func_obj, new_target)) new_target = bf->func_obj; return JS_CallConstructor2(ctx, bf->func_obj, new_target, arg_count, arg_buf); } else { return JS_Call(ctx, bf->func_obj, bf->this_val, arg_count, arg_buf); } } 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)) { /* XXX: should set a specific flag to avoid catching */ JS_ThrowInternalError(ctx, "interrupted"); JS_SetUncatchableError(ctx, ctx->current_exception, TRUE); 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; } } /* argument of OP_special_object */ typedef enum { OP_SPECIAL_OBJECT_ARGUMENTS, OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, OP_SPECIAL_OBJECT_THIS_FUNC, OP_SPECIAL_OBJECT_NEW_TARGET, OP_SPECIAL_OBJECT_HOME_OBJECT, OP_SPECIAL_OBJECT_VAR_OBJECT, OP_SPECIAL_OBJECT_IMPORT_META, } OPSpecialObjectEnum; #define FUNC_RET_AWAIT 0 #define FUNC_RET_YIELD 1 #define FUNC_RET_YIELD_STAR 2 /* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, int argc, JSValue *argv, int flags) { 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 #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(ctx)) return JS_EXCEPTION; if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { if (flags & JS_CALL_FLAG_GENERATOR) { JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); /* func_obj get contains a pointer to JSFuncAsyncState */ /* the stack frame is already allocated */ sf = &s->frame; p = JS_VALUE_GET_OBJ(sf->cur_func); b = p->u.func.function_bytecode; var_refs = p->u.func.var_refs; local_buf = arg_buf = sf->arg_buf; var_buf = sf->var_buf; stack_buf = sf->var_buf + b->var_count; sp = sf->cur_sp; sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ pc = sf->cur_pc; sf->prev_frame = ctx->current_stack_frame; ctx->current_stack_frame = sf; if (s->throw_flag) goto exception; else goto restart; } else { 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 = ctx->rt->class_array[p->class_id].call; if (!call_func) { not_a_function: return JS_ThrowTypeError(ctx, "not a function"); } return call_func(ctx, func_obj, this_obj, argc, (JSValueConst *)argv, flags); } b = p->u.func.function_bytecode; 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(ctx, alloca_size)) return JS_ThrowStackOverflow(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(ctx, argv[i]); for(; i < b->arg_count; i++) arg_buf[i] = JS_UNDEFINED; 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_UNDEFINED; stack_buf = var_buf + b->var_count; sp = stack_buf; pc = b->byte_code_buf; sf->prev_frame = ctx->current_stack_frame; ctx->current_stack_frame = sf; restart: for(;;) { int call_argc; JSValue *call_argv; 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; 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_undefined): *sp++ = JS_UNDEFINED; 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; if (!(b->js_mode & JS_MODE_STRICT)) { uint32_t tag = JS_VALUE_GET_TAG(this_obj); if (likely(tag == JS_TAG_OBJECT)) goto normal_this; if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { val = JS_DupValue(ctx, ctx->global_obj); } else { val = JS_ToObject(ctx, this_obj); if (JS_IsException(val)) goto exception; } } else { normal_this: 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_ARGUMENTS: *sp++ = js_build_arguments(ctx, argc, (JSValueConst *)argv); if (unlikely(JS_IsException(sp[-1]))) goto exception; break; case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: *sp++ = js_build_mapped_arguments(ctx, argc, (JSValueConst *)argv, sf, min_int(argc, b->arg_count)); if (unlikely(JS_IsException(sp[-1]))) goto exception; break; 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_HOME_OBJECT: { JSObject *p1; p1 = p->u.func.home_object; if (unlikely(!p1)) *sp++ = JS_UNDEFINED; else *sp++ = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); } break; case OP_SPECIAL_OBJECT_VAR_OBJECT: *sp++ = JS_NewObjectProto(ctx, JS_NULL); if (unlikely(JS_IsException(sp[-1]))) goto exception; break; case OP_SPECIAL_OBJECT_IMPORT_META: *sp++ = js_import_meta(ctx); 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; ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, JS_UNDEFINED, 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_constructor): { call_argc = get_u16(pc); pc += 2; call_argv = sp - call_argc; sf->cur_pc = pc; ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], call_argv[-1], call_argc, call_argv, 0); if (unlikely(JS_IsException(ret_val))) goto exception; for(i = -2; i < call_argc; i++) JS_FreeValue(ctx, call_argv[i]); sp -= call_argc + 2; *sp++ = ret_val; } BREAK; CASE(OP_call_method): CASE(OP_tail_call_method): { call_argc = get_u16(pc); pc += 2; call_argv = sp - call_argc; sf->cur_pc = pc; ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], JS_UNDEFINED, 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_UNDEFINED; if (ret < 0) { JS_FreeValue(ctx, ret_val); goto exception; } } sp -= call_argc; *sp++ = ret_val; } BREAK; CASE(OP_apply): { int magic; magic = get_u16(pc); pc += 2; 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_UNDEFINED; goto done; CASE(OP_check_ctor_return): /* return TRUE if 'this' should be returned */ if (!JS_IsObject(sp[-1])) { if (!JS_IsUndefined(sp[-1])) { JS_ThrowTypeError(ctx, "derived class constructor must return an object or undefined"); goto exception; } sp[0] = JS_TRUE; } else { sp[0] = JS_FALSE; } sp++; BREAK; CASE(OP_check_ctor): if (JS_IsUndefined(new_target)) { JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); goto exception; } BREAK; CASE(OP_check_brand): if (JS_CheckBrand(ctx, sp[-2], sp[-1]) < 0) goto exception; BREAK; CASE(OP_add_brand): if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) goto exception; JS_FreeValue(ctx, sp[-2]); JS_FreeValue(ctx, sp[-1]); sp -= 2; BREAK; CASE(OP_throw): JS_Throw(ctx, *--sp); goto exception; CASE(OP_throw_var): #define JS_THROW_VAR_RO 0 #define JS_THROW_VAR_REDECL 1 #define JS_THROW_VAR_UNINITIALIZED 2 #define JS_THROW_VAR_DELETE_SUPER 3 { 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_VAR_DELETE_SUPER) JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); 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) - 1; 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_UNDEFINED; ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, JS_EVAL_TYPE_DIRECT, scope_idx); } else { ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, JS_UNDEFINED, 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) - 1; pc += 2; 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_UNDEFINED; ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, JS_EVAL_TYPE_DIRECT, scope_idx); } else { ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, 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_UNDEFINED, sp[-2], sp[-1]); sp--; } BREAK; CASE(OP_get_super_ctor): { JSValue proto; proto = JS_DupValue(ctx, JS_GetPrototype(ctx, sp[-1])); if (JS_IsException(proto)) goto exception; if (!JS_IsConstructor(ctx, proto)) { JS_FreeValue(ctx, proto); JS_ThrowTypeError(ctx, "not a constructor"); goto exception; } JS_FreeValue(ctx, sp[-1]); sp[-1] = proto; } BREAK; CASE(OP_get_super): { JSValue proto; proto = JS_DupValue(ctx, JS_GetPrototype(ctx, sp[-1])); if (JS_IsException(proto)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = proto; } BREAK; CASE(OP_import): { JSValue val; val = js_dynamic_import(ctx, sp[-1]); if (JS_IsException(val)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = val; } BREAK; CASE(OP_check_var): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; 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; 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; 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; /* 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; 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; 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; 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_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL); 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_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL); 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_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL); 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_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL); 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_ThrowReferenceErrorUninitialized(ctx, JS_ATOM_NULL); 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, FALSE); } 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(ctx->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; 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_UNDEFINED) { 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; if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { 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_UNDEFINED) { 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_UNDEFINED) { 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_for_in_start): if (js_for_in_start(ctx, sp)) goto exception; BREAK; CASE(OP_for_in_next): if (js_for_in_next(ctx, sp)) goto exception; sp += 2; BREAK; CASE(OP_for_of_start): if (js_for_of_start(ctx, sp, FALSE)) goto exception; sp += 1; *sp++ = JS_NewCatchOffset(ctx, 0); BREAK; CASE(OP_for_of_next): { int offset = -3 - pc[0]; pc += 1; if (js_for_of_next(ctx, sp, offset)) goto exception; sp += 2; } BREAK; CASE(OP_for_await_of_start): if (js_for_of_start(ctx, sp, TRUE)) goto exception; sp += 1; *sp++ = JS_NewCatchOffset(ctx, 0); BREAK; CASE(OP_for_await_of_next): if (js_for_await_of_next(ctx, sp)) goto exception; sp += 1; BREAK; CASE(OP_iterator_get_value_done): if (js_iterator_get_value_done(ctx, sp)) goto exception; sp += 1; BREAK; CASE(OP_iterator_close): sp--; /* drop the catch offset to avoid getting caught by exception */ JS_FreeValue(ctx, sp[-1]); /* drop the next method */ sp--; if (!JS_IsUndefined(sp[-1])) { if (JS_IteratorClose(ctx, sp[-1], FALSE)) goto exception; JS_FreeValue(ctx, sp[-1]); } sp--; BREAK; CASE(OP_iterator_close_return): { JSValue ret_val; /* iter_obj next catch_offset ... ret_val -> ret_eval iter_obj next catch_offset */ 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 + 3)) { JS_ThrowInternalError(ctx, "iterator_close_return"); JS_FreeValue(ctx, ret_val); goto exception; } sp[0] = sp[-1]; sp[-1] = sp[-2]; sp[-2] = sp[-3]; sp[-3] = ret_val; sp++; } BREAK; CASE(OP_async_iterator_close): /* iter_obj next catch_offset -> value flag */ { JSValue ret, method; int ret_flag; method = JS_GetProperty(ctx, sp[-3], JS_ATOM_return); if (JS_IsException(method)) goto exception; if (JS_IsUndefined(method) || JS_IsNull(method)) { ret = JS_UNDEFINED; ret_flag = TRUE; } else { ret = JS_CallFree(ctx, method, sp[-3], 0, NULL); if (JS_IsException(ret)) goto exception; ret_flag = FALSE; } JS_FreeValue(ctx, sp[-3]); JS_FreeValue(ctx, sp[-2]); JS_FreeValue(ctx, sp[-1]); sp[-3] = ret; sp[-2] = JS_NewBool(ctx, ret_flag); sp -= 1; } BREAK; CASE(OP_async_iterator_next): /* stack: iter_obj next catch_offset val */ { JSValue ret; ret = JS_Call(ctx, sp[-3], sp[-4], 1, (JSValueConst *)(sp - 1)); if (JS_IsException(ret)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret; } BREAK; CASE(OP_async_iterator_get): /* stack: iter_obj next catch_offset val */ { JSValue method, ret; BOOL ret_flag; int flags; flags = *pc++; if (flags == 2) { JS_ThrowTypeError(ctx, "iterator does not have a throw method"); goto exception; } method = JS_GetProperty(ctx, sp[-4], flags ? JS_ATOM_throw : JS_ATOM_return); if (JS_IsException(method)) goto exception; if (JS_IsUndefined(method) || JS_IsNull(method)) { ret_flag = TRUE; } else { ret = JS_CallFree(ctx, method, sp[-4], 1, (JSValueConst *)(sp - 1)); if (JS_IsException(ret)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret; ret_flag = FALSE; } sp[0] = JS_NewBool(ctx, ret_flag); sp += 1; } BREAK; CASE(OP_lnot): { int res; JSValue op1; op1 = sp[-1]; if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { 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; atom = get_u32(pc); pc += 4; val = JS_GetProperty(ctx, sp[-1], atom); if (unlikely(JS_IsException(val))) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = val; } BREAK; CASE(OP_get_field2): { JSValue val; JSAtom atom; atom = get_u32(pc); pc += 4; val = JS_GetProperty(ctx, sp[-1], atom); if (unlikely(JS_IsException(val))) goto exception; *sp++ = val; } BREAK; CASE(OP_put_field): { int ret; JSAtom atom; atom = get_u32(pc); pc += 4; ret = JS_SetPropertyInternal(ctx, sp[-2], atom, sp[-1], JS_PROP_THROW_STRICT); JS_FreeValue(ctx, sp[-2]); sp -= 2; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_private_symbol): { JSAtom atom; JSValue val; atom = get_u32(pc); pc += 4; val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); if (JS_IsException(val)) goto exception; *sp++ = val; } BREAK; CASE(OP_get_private_field): { JSValue val; val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); JS_FreeValue(ctx, sp[-1]); JS_FreeValue(ctx, sp[-2]); sp[-2] = val; sp--; if (unlikely(JS_IsException(val))) goto exception; } BREAK; CASE(OP_put_private_field): { int ret; ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); JS_FreeValue(ctx, sp[-3]); JS_FreeValue(ctx, sp[-1]); sp -= 3; if (unlikely(ret < 0)) goto exception; } BREAK; CASE(OP_define_private_field): { int ret; ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); 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; proto = sp[-1]; if (JS_IsObject(proto) || JS_IsNull(proto)) { if (JS_SetPrototypeInternal(ctx, sp[-2], proto, TRUE) < 0) goto exception; } JS_FreeValue(ctx, proto); sp--; } BREAK; CASE(OP_set_home_object): js_method_set_home_object(ctx, sp[-1], sp[-2]); 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_UNDEFINED; getter = JS_UNDEFINED; setter = JS_UNDEFINED; 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): { int class_flags; JSAtom atom; atom = get_u32(pc); class_flags = pc[4]; pc += 5; if (js_op_define_class(ctx, sp, atom, class_flags, var_refs, sf, (opcode == OP_define_class_computed)) < 0) goto exception; } BREAK; CASE(OP_get_array_el): { JSValue val; 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; val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); sp[-1] = val; if (unlikely(JS_IsException(val))) goto exception; } BREAK; CASE(OP_get_ref_value): { JSValue val; if (unlikely(JS_IsUndefined(sp[-2]))) { JSAtom atom = JS_ValueToAtom(ctx, sp[-1]); if (atom != JS_ATOM_NULL) { JS_ThrowReferenceErrorNotDefined(ctx, atom); JS_FreeAtom(ctx, atom); } goto exception; } val = JS_GetPropertyValue(ctx, sp[-2], JS_DupValue(ctx, sp[-1])); if (unlikely(JS_IsException(val))) goto exception; sp[0] = val; sp++; } BREAK; CASE(OP_get_super_value): { JSValue val; JSAtom atom; atom = JS_ValueToAtom(ctx, sp[-1]); if (unlikely(atom == JS_ATOM_NULL)) goto exception; val = JS_GetPropertyInternal(ctx, sp[-2], atom, sp[-3], FALSE); JS_FreeAtom(ctx, atom); if (unlikely(JS_IsException(val))) goto exception; JS_FreeValue(ctx, sp[-1]); JS_FreeValue(ctx, sp[-2]); JS_FreeValue(ctx, sp[-3]); sp[-3] = val; sp -= 2; } BREAK; CASE(OP_put_array_el): { int ret; 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; if (unlikely(JS_IsUndefined(sp[-3]))) { if (is_strict_mode(ctx)) { JSAtom atom = JS_ValueToAtom(ctx, sp[-2]); if (atom != JS_ATOM_NULL) { JS_ThrowReferenceErrorNotDefined(ctx, atom); JS_FreeAtom(ctx, atom); } goto exception; } else { sp[-3] = JS_DupValue(ctx, ctx->global_obj); } } 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_super_value): { int ret; JSAtom atom; if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { JS_ThrowTypeErrorNotAnObject(ctx); goto exception; } atom = JS_ValueToAtom(ctx, sp[-2]); if (unlikely(atom == JS_ATOM_NULL)) goto exception; ret = JS_SetPropertyGeneric(ctx, JS_VALUE_GET_OBJ(sp[-3]), atom, sp[-1], sp[-4], JS_PROP_THROW_STRICT); JS_FreeAtom(ctx, atom); JS_FreeValue(ctx, sp[-4]); JS_FreeValue(ctx, sp[-3]); JS_FreeValue(ctx, sp[-2]); sp -= 4; if (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_append): /* array pos enumobj -- array pos */ { if (js_append_enumerate(ctx, sp)) goto exception; JS_FreeValue(ctx, *--sp); } 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++; 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): { 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 add_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 { add_slow: if (js_add_slow(ctx, sp)) goto exception; sp--; } } BREAK; CASE(OP_add_loc): { JSValue ops[2]; int idx; idx = *pc; pc += 1; ops[0] = var_buf[idx]; ops[1] = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(ops[0], ops[1]))) { int64_t r; r = (int64_t)JS_VALUE_GET_INT(ops[0]) + JS_VALUE_GET_INT(ops[1]); if (unlikely((int)r != r)) goto add_loc_slow; var_buf[idx] = JS_NewInt32(ctx, r); sp--; } else if (JS_VALUE_GET_TAG(ops[0]) == JS_TAG_STRING) { sp--; ops[1] = JS_ToPrimitiveFree(ctx, ops[1], HINT_NONE); if (JS_IsException(ops[1])) { goto exception; } /* XXX: should not modify the variable in case of exception */ ops[0] = JS_ConcatString(ctx, ops[0], ops[1]); if (JS_IsException(ops[0])) { var_buf[idx] = JS_UNDEFINED; goto exception; } var_buf[idx] = ops[0]; } else { add_loc_slow: /* XXX: should not modify the variable in case of exception */ sp--; /* In case of exception, js_add_slow frees ops[0] and ops[1]. */ /* XXX: change API */ if (js_add_slow(ctx, ops + 2)) { var_buf[idx] = JS_UNDEFINED; goto exception; } var_buf[idx] = ops[0]; } } BREAK; CASE(OP_sub): { 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): { 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)) { #ifdef CONFIG_BIGNUM if (unlikely(sf->js_mode & JS_MODE_MATH) && (r < -MAX_SAFE_INTEGER || r > MAX_SAFE_INTEGER)) goto binary_arith_slow; #endif 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)) { #ifdef CONFIG_BIGNUM if (unlikely(sf->js_mode & JS_MODE_MATH)) goto binary_arith_slow; #endif 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): { JSValue op1, op2; op1 = sp[-2]; op2 = sp[-1]; if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { int v1, v2; if (unlikely(sf->js_mode & JS_MODE_MATH)) goto binary_arith_slow; 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): #ifdef CONFIG_BIGNUM CASE(OP_math_mod): #endif { 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: 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 { 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 { 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: 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: if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; } } BREAK; CASE(OP_post_inc): CASE(OP_post_dec): 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: if (js_unary_arith_slow(ctx, var_buf + idx + 1, OP_inc)) goto exception; } } 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: if (js_unary_arith_slow(ctx, var_buf + idx + 1, OP_dec)) goto exception; } } 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 { 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); #ifdef CONFIG_BIGNUM { int64_t r; if (unlikely(sf->js_mode & JS_MODE_MATH)) { if (v2 > 0x1f) goto shl_slow; r = (int64_t)v1 << v2; if ((int)r != r) goto shl_slow; } else { v2 &= 0x1f; } } #else v2 &= 0x1f; #endif sp[-2] = JS_NewInt32(ctx, v1 << v2); sp--; } else { #ifdef CONFIG_BIGNUM shl_slow: #endif if (js_binary_logic_slow(ctx, sp, opcode)) goto exception; 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); /* v1 >>> v2 retains its JS semantics if CONFIG_BIGNUM */ v2 &= 0x1f; sp[-2] = JS_NewUint32(ctx, (uint32_t)JS_VALUE_GET_INT(op1) >> v2); sp--; } else { if (js_shr_slow(ctx, sp)) goto exception; 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); #ifdef CONFIG_BIGNUM if (unlikely(v2 > 0x1f)) { if (unlikely(sf->js_mode & JS_MODE_MATH)) goto sar_slow; else v2 &= 0x1f; } #else v2 &= 0x1f; #endif sp[-2] = JS_NewInt32(ctx, (int)JS_VALUE_GET_INT(op1) >> v2); sp--; } else { #ifdef CONFIG_BIGNUM sar_slow: #endif if (js_binary_logic_slow(ctx, sp, opcode)) goto exception; 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_binary_logic_slow(ctx, sp, opcode)) goto exception; 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_binary_logic_slow(ctx, sp, opcode)) goto exception; 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_binary_logic_slow(ctx, sp, opcode)) goto exception; 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 { \ 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)); #ifdef CONFIG_BIGNUM CASE(OP_mul_pow10): if (ctx->rt->bigfloat_ops.mul_pow10(ctx, sp)) goto exception; sp--; BREAK; #endif CASE(OP_in): if (js_operator_in(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_instanceof): if (js_operator_instanceof(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_typeof): { JSValue op1; JSAtom atom; op1 = sp[-1]; atom = js_operator_typeof(ctx, op1); JS_FreeValue(ctx, op1); sp[-1] = JS_AtomToString(ctx, atom); } BREAK; CASE(OP_delete): if (js_operator_delete(ctx, sp)) goto exception; sp--; BREAK; CASE(OP_delete_var): { JSAtom atom; int ret; atom = get_u32(pc); pc += 4; ret = JS_DeleteProperty(ctx, ctx->global_obj, atom, 0); 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) { 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: 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_to_propkey2): /* must be tested first */ if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { JS_ThrowTypeError(ctx, "value has no property"); goto exception; } switch (JS_VALUE_GET_TAG(sp[-1])) { case JS_TAG_INT: case JS_TAG_STRING: case JS_TAG_SYMBOL: break; default: 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; #if 0 CASE(OP_to_string): if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_STRING) { ret_val = JS_ToString(ctx, sp[-1]); if (JS_IsException(ret_val)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = ret_val; } BREAK; #endif CASE(OP_with_get_var): CASE(OP_with_put_var): CASE(OP_with_delete_var): CASE(OP_with_make_ref): CASE(OP_with_get_ref): CASE(OP_with_get_ref_undef): { JSAtom atom; int32_t diff; JSValue obj, val; int ret, is_with; atom = get_u32(pc); diff = get_u32(pc + 4); is_with = pc[8]; pc += 9; obj = sp[-1]; ret = JS_HasProperty(ctx, obj, atom); if (unlikely(ret < 0)) goto exception; if (ret) { if (is_with) { ret = js_has_unscopable(ctx, obj, atom); if (unlikely(ret < 0)) goto exception; if (ret) goto no_with; } switch (opcode) { case OP_with_get_var: val = JS_GetProperty(ctx, obj, atom); if (unlikely(JS_IsException(val))) goto exception; set_value(ctx, &sp[-1], val); break; case OP_with_put_var: ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], JS_PROP_THROW_STRICT); JS_FreeValue(ctx, sp[-1]); sp -= 2; if (unlikely(ret < 0)) goto exception; break; case OP_with_delete_var: ret = JS_DeleteProperty(ctx, obj, atom, 0); if (unlikely(ret < 0)) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = JS_NewBool(ctx, ret); break; case OP_with_make_ref: /* produce a pair object/propname on the stack */ *sp++ = JS_AtomToValue(ctx, atom); break; case OP_with_get_ref: /* produce a pair object/method on the stack */ val = JS_GetProperty(ctx, obj, atom); if (unlikely(JS_IsException(val))) goto exception; *sp++ = val; break; case OP_with_get_ref_undef: /* produce a pair undefined/function on the stack */ val = JS_GetProperty(ctx, obj, atom); if (unlikely(JS_IsException(val))) goto exception; JS_FreeValue(ctx, sp[-1]); sp[-1] = JS_UNDEFINED; *sp++ = val; break; } pc += diff - 5; } else { no_with: /* if not jumping, drop the object argument */ JS_FreeValue(ctx, sp[-1]); sp--; } } BREAK; CASE(OP_await): ret_val = JS_NewInt32(ctx, FUNC_RET_AWAIT); goto done_generator; CASE(OP_yield): ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD); goto done_generator; CASE(OP_yield_star): CASE(OP_async_yield_star): ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD_STAR); goto done_generator; CASE(OP_return_async): CASE(OP_initial_yield): ret_val = JS_UNDEFINED; goto done_generator; CASE(OP_nop): BREAK; CASE(OP_is_undefined_or_null): if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { goto set_true; } else { goto free_and_set_false; } #if SHORT_OPCODES CASE(OP_is_undefined): if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { goto set_true; } else { goto free_and_set_false; } CASE(OP_is_null): if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { goto set_true; } else { goto free_and_set_false; } CASE(OP_is_function): if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { goto free_and_set_true; } else { goto free_and_set_false; } free_and_set_true: JS_FreeValue(ctx, sp[-1]); #endif 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, ctx->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, ctx->current_exception, NULL, 0, 0); } if (!JS_IsUncatchableError(ctx, ctx->current_exception)) { 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) { /* enumerator: close it with a throw */ JS_FreeValue(ctx, sp[-1]); /* drop the next method */ sp--; JS_IteratorClose(ctx, sp[-1], TRUE); } else { *sp++ = ctx->current_exception; ctx->current_exception = JS_NULL; pc = b->byte_code_buf + pos; goto restart; } } } } ret_val = JS_EXCEPTION; /* the local variables are freed by the caller in the generator case. Hence the label 'done' should never be reached in a generator function. */ if (b->func_kind != JS_FUNC_NORMAL) { done_generator: sf->cur_pc = pc; sf->cur_sp = sp; } else { done: if (unlikely(!list_empty(&sf->var_ref_list))) { /* variable references reference the stack: must close them */ close_var_refs(ctx->rt, sf); } /* free the local variables and stack */ for(pval = local_buf; pval < sp; pval++) { JS_FreeValue(ctx, *pval); } } ctx->current_stack_frame = sf->prev_frame; 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_UNDEFINED, 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_UNDEFINED, argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); JS_FreeValue(ctx, func_obj); return res; } static JSValue js_get_prototype_from_ctor(JSContext *ctx, JSValueConst ctor, JSValueConst def_proto) { JSValue proto; proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); if (JS_IsException(proto)) return proto; if (!JS_IsObject(proto)) { JS_FreeValue(ctx, proto); proto = JS_DupValue(ctx, def_proto); } return proto; } static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor, int class_id) { JSValue proto, obj; if (JS_IsUndefined(ctor)) { proto = JS_DupValue(ctx, ctx->class_proto[class_id]); } else { proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); if (JS_IsException(proto)) return proto; if (!JS_IsObject(proto)) { JS_FreeValue(ctx, proto); /* check if revoked proxy */ { JSProxyData *s = JS_GetOpaque(ctor, JS_CLASS_PROXY); if (s && s->is_revoked) return JS_ThrowTypeErrorRevokedProxy(ctx); } /* XXX: should use the ctor realm instead of 'ctx' */ proto = JS_DupValue(ctx, ctx->class_proto[class_id]); } } obj = JS_NewObjectProtoClass(ctx, proto, class_id); JS_FreeValue(ctx, proto); return obj; } /* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ static JSValue JS_CallConstructorInternal(JSContext *ctx, JSValueConst func_obj, JSValueConst new_target, int argc, JSValue *argv, int flags) { JSObject *p; JSFunctionBytecode *b; if (js_poll_interrupts(ctx)) return JS_EXCEPTION; flags |= JS_CALL_FLAG_CONSTRUCTOR; 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->is_constructor)) return JS_ThrowTypeError(ctx, "not a constructor"); if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { JSClassCall *call_func; call_func = ctx->rt->class_array[p->class_id].call; if (!call_func) { not_a_function: return JS_ThrowTypeError(ctx, "not a function"); } return call_func(ctx, func_obj, new_target, argc, (JSValueConst *)argv, flags); } b = p->u.func.function_bytecode; if (b->is_derived_class_constructor) { return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); } else { JSValue obj, ret; /* legacy constructor behavior */ obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); if (JS_IsException(obj)) return JS_EXCEPTION; ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || JS_IsException(ret)) { JS_FreeValue(ctx, obj); return ret; } else { JS_FreeValue(ctx, ret); return obj; } } } JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj, JSValueConst new_target, int argc, JSValueConst *argv) { return JS_CallConstructorInternal(ctx, func_obj, new_target, argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); } JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj, int argc, JSValueConst *argv) { return JS_CallConstructorInternal(ctx, func_obj, func_obj, argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); } 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; } /* JSAsyncFunctionState (used by generator and async functions) */ static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv) { JSObject *p; JSFunctionBytecode *b; JSStackFrame *sf; int local_count, i, arg_buf_len, n; sf = &s->frame; init_list_head(&sf->var_ref_list); p = JS_VALUE_GET_OBJ(func_obj); b = p->u.func.function_bytecode; sf->js_mode = b->js_mode; sf->cur_pc = b->byte_code_buf; arg_buf_len = max_int(b->arg_count, argc); local_count = arg_buf_len + b->var_count + b->stack_size; sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1)); if (!sf->arg_buf) return -1; sf->cur_func = JS_DupValue(ctx, func_obj); s->this_val = JS_DupValue(ctx, this_obj); s->argc = argc; sf->arg_count = arg_buf_len; sf->var_buf = sf->arg_buf + arg_buf_len; sf->cur_sp = sf->var_buf + b->var_count; for(i = 0; i < argc; i++) sf->arg_buf[i] = JS_DupValue(ctx, argv[i]); n = arg_buf_len + b->var_count; for(i = argc; i < n; i++) sf->arg_buf[i] = JS_UNDEFINED; return 0; } static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, JS_MarkFunc *mark_func) { JSStackFrame *sf; JSValue *sp; sf = &s->frame; JS_MarkValue(rt, sf->cur_func, mark_func); JS_MarkValue(rt, s->this_val, mark_func); if (sf->cur_sp) { /* if the function is running, cur_sp is not known so we cannot mark the stack. Marking the variables is not needed because a running function cannot be part of a removable cycle */ for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) JS_MarkValue(rt, *sp, mark_func); } } static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) { JSStackFrame *sf; JSValue *sp; sf = &s->frame; /* close the closure variables. */ close_var_refs(rt, sf); if (sf->arg_buf) { /* cannot free the function if it is running */ assert(sf->cur_sp != NULL); for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { JS_FreeValueRT(rt, *sp); } js_free_rt(rt, sf->arg_buf); } JS_FreeValueRT(rt, sf->cur_func); JS_FreeValueRT(rt, s->this_val); } static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) { JSValue func_obj; /* the tag does not matter provided it is not an object */ func_obj = JS_MKPTR(JS_TAG_INT, s); return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, s->argc, s->frame.arg_buf, JS_CALL_FLAG_GENERATOR); } /* Generators */ typedef enum JSGeneratorStateEnum { JS_GENERATOR_STATE_SUSPENDED_START, JS_GENERATOR_STATE_SUSPENDED_YIELD, JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, JS_GENERATOR_STATE_EXECUTING, JS_GENERATOR_STATE_COMPLETED, } JSGeneratorStateEnum; typedef struct JSGeneratorData { JSGeneratorStateEnum state; JSAsyncFunctionState func_state; } JSGeneratorData; static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) { if (s->state == JS_GENERATOR_STATE_COMPLETED) return; async_func_free(rt, &s->func_state); s->state = JS_GENERATOR_STATE_COMPLETED; } static void js_generator_finalizer(JSRuntime *rt, JSValue obj) { JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); if (s) { free_generator_stack_rt(rt, s); js_free_rt(rt, s); } } static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) { free_generator_stack_rt(ctx->rt, s); } static void js_generator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSGeneratorData *s = p->u.generator_data; if (!s || s->state == JS_GENERATOR_STATE_COMPLETED) return; async_func_mark(rt, &s->func_state, mark_func); } /* XXX: use enum */ #define GEN_MAGIC_NEXT 0 #define GEN_MAGIC_RETURN 1 #define GEN_MAGIC_THROW 2 static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, BOOL *pdone, int magic) { JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); JSStackFrame *sf; JSValue ret, func_ret; JSValueConst iter_args[1]; *pdone = TRUE; if (!s) return JS_ThrowTypeError(ctx, "not a generator"); sf = &s->func_state.frame; redo: switch(s->state) { default: case JS_GENERATOR_STATE_SUSPENDED_START: if (magic == GEN_MAGIC_NEXT) { goto exec_no_arg; } else { free_generator_stack(ctx, s); goto done; } break; case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: { int done; JSValue method, iter_obj; iter_obj = sf->cur_sp[-2]; if (magic == GEN_MAGIC_NEXT) { method = JS_DupValue(ctx, sf->cur_sp[-1]); } else { method = JS_GetProperty(ctx, iter_obj, magic == GEN_MAGIC_RETURN ? JS_ATOM_return : JS_ATOM_throw); if (JS_IsException(method)) goto iter_exception; } if (magic != GEN_MAGIC_NEXT && (JS_IsUndefined(method) || JS_IsNull(method))) { /* default action */ if (magic == GEN_MAGIC_RETURN) { ret = JS_DupValue(ctx, argv[0]); goto iter_done; } else { if (JS_IteratorClose(ctx, iter_obj, FALSE)) goto iter_exception; JS_ThrowTypeError(ctx, "iterator does not have a throw method"); goto iter_exception; } } ret = JS_IteratorNext2(ctx, iter_obj, method, argc, argv, &done); JS_FreeValue(ctx, method); if (JS_IsException(ret)) { iter_exception: goto exec_throw; } /* if not done, the iterator returns the exact object returned by 'method' */ if (done == 2) { JSValue done_val, value; done_val = JS_GetProperty(ctx, ret, JS_ATOM_done); if (JS_IsException(done_val)) { JS_FreeValue(ctx, ret); goto iter_exception; } done = JS_ToBoolFree(ctx, done_val); if (done) { value = JS_GetProperty(ctx, ret, JS_ATOM_value); JS_FreeValue(ctx, ret); if (JS_IsException(value)) goto iter_exception; ret = value; goto iter_done; } else { *pdone = 2; } } else { if (done) { /* 'yield *' returns the value associated to done = true */ iter_done: JS_FreeValue(ctx, sf->cur_sp[-2]); JS_FreeValue(ctx, sf->cur_sp[-1]); sf->cur_sp--; goto exec_arg; } else { *pdone = FALSE; } } break; } break; case JS_GENERATOR_STATE_SUSPENDED_YIELD: /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ ret = JS_DupValue(ctx, argv[0]); if (magic == GEN_MAGIC_THROW) { JS_Throw(ctx, ret); exec_throw: s->func_state.throw_flag = TRUE; } else { exec_arg: sf->cur_sp[-1] = ret; sf->cur_sp[0] = JS_NewBool(ctx, (magic == GEN_MAGIC_RETURN)); sf->cur_sp++; exec_no_arg: s->func_state.throw_flag = FALSE; } s->state = JS_GENERATOR_STATE_EXECUTING; func_ret = async_func_resume(ctx, &s->func_state); s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; if (JS_IsException(func_ret)) { /* finalize the execution in case of exception */ free_generator_stack(ctx, s); return func_ret; } if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { /* 'yield *' */ s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; iter_args[0] = JS_UNDEFINED; argc = 1; argv = iter_args; goto redo; } else { /* get the return the yield value at the top of the stack */ ret = sf->cur_sp[-1]; sf->cur_sp[-1] = JS_UNDEFINED; *pdone = FALSE; } } else { /* end of iterator */ ret = sf->cur_sp[-1]; sf->cur_sp[-1] = JS_UNDEFINED; JS_FreeValue(ctx, func_ret); free_generator_stack(ctx, s); } break; case JS_GENERATOR_STATE_COMPLETED: done: /* execution is finished */ switch(magic) { default: case GEN_MAGIC_NEXT: ret = JS_UNDEFINED; break; case GEN_MAGIC_RETURN: ret = JS_DupValue(ctx, argv[0]); break; case GEN_MAGIC_THROW: ret = JS_Throw(ctx, JS_DupValue(ctx, argv[0])); break; } break; case JS_GENERATOR_STATE_EXECUTING: ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); break; } return ret; } static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSValue obj, func_ret; JSGeneratorData *s; s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; s->state = JS_GENERATOR_STATE_SUSPENDED_START; if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { s->state = JS_GENERATOR_STATE_COMPLETED; goto fail; } /* execute the function up to 'OP_initial_yield' */ func_ret = async_func_resume(ctx, &s->func_state); if (JS_IsException(func_ret)) goto fail; JS_FreeValue(ctx, func_ret); obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); if (JS_IsException(obj)) goto fail; JS_SetOpaque(obj, s); return obj; fail: free_generator_stack_rt(ctx->rt, s); js_free(ctx, s); return JS_EXCEPTION; } /* AsyncFunction */ static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s) { if (s->is_active) { async_func_free(rt, &s->func_state); s->is_active = FALSE; } } static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s) { js_async_function_terminate(rt, s); JS_FreeValueRT(rt, s->resolving_funcs[0]); JS_FreeValueRT(rt, s->resolving_funcs[1]); remove_gc_object(&s->header); js_free_rt(rt, s); } static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s) { if (--s->header.ref_count == 0) { js_async_function_free0(rt, s); } } static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSAsyncFunctionData *s = p->u.async_function_data; if (s) { js_async_function_free(rt, s); } } static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSAsyncFunctionData *s = p->u.async_function_data; if (s) { mark_func(rt, &s->header); } } static int js_async_function_resolve_create(JSContext *ctx, JSAsyncFunctionData *s, JSValue *resolving_funcs) { int i; JSObject *p; for(i = 0; i < 2; i++) { resolving_funcs[i] = JS_NewObjectProtoClass(ctx, ctx->function_proto, JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); if (JS_IsException(resolving_funcs[i])) { if (i == 1) JS_FreeValue(ctx, resolving_funcs[0]); return -1; } p = JS_VALUE_GET_OBJ(resolving_funcs[i]); s->header.ref_count++; p->u.async_function_data = s; } return 0; } static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s) { JSValue func_ret, ret2; func_ret = async_func_resume(ctx, &s->func_state); if (JS_IsException(func_ret)) { JSValue error; fail: error = JS_GetException(ctx); ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&error); JS_FreeValue(ctx, error); js_async_function_terminate(ctx->rt, s); JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ } else { JSValue value; value = s->func_state.frame.cur_sp[-1]; s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; if (JS_IsUndefined(func_ret)) { /* function returned */ ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&value); JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ JS_FreeValue(ctx, value); js_async_function_terminate(ctx->rt, s); } else { JSValue promise, resolving_funcs[2], resolving_funcs1[2]; int i, res; /* await */ JS_FreeValue(ctx, func_ret); /* not used */ promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&value, 0); JS_FreeValue(ctx, value); if (JS_IsException(promise)) goto fail; if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { JS_FreeValue(ctx, promise); goto fail; } /* Note: no need to create 'thrownawayCapability' as in the spec */ for(i = 0; i < 2; i++) resolving_funcs1[i] = JS_UNDEFINED; res = perform_promise_then(ctx, promise, (JSValueConst *)resolving_funcs, (JSValueConst *)resolving_funcs1); JS_FreeValue(ctx, promise); for(i = 0; i < 2; i++) JS_FreeValue(ctx, resolving_funcs[i]); if (res) goto fail; } } } static JSValue js_async_function_resolve_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSObject *p = JS_VALUE_GET_OBJ(func_obj); JSAsyncFunctionData *s = p->u.async_function_data; BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; JSValueConst arg; if (argc > 0) arg = argv[0]; else arg = JS_UNDEFINED; s->func_state.throw_flag = is_reject; if (is_reject) { JS_Throw(ctx, JS_DupValue(ctx, arg)); } else { /* return value of await */ s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg); } js_async_function_resume(ctx, s); return JS_UNDEFINED; } static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSValue promise; JSAsyncFunctionData *s; s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; s->header.ref_count = 1; add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); s->is_active = FALSE; s->resolving_funcs[0] = JS_UNDEFINED; s->resolving_funcs[1] = JS_UNDEFINED; promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); if (JS_IsException(promise)) goto fail; if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { fail: JS_FreeValue(ctx, promise); js_async_function_free(ctx->rt, s); return JS_EXCEPTION; } s->is_active = TRUE; js_async_function_resume(ctx, s); js_async_function_free(ctx->rt, s); return promise; } /* AsyncGenerator */ typedef enum JSAsyncGeneratorStateEnum { JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, JS_ASYNC_GENERATOR_STATE_EXECUTING, JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, JS_ASYNC_GENERATOR_STATE_COMPLETED, } JSAsyncGeneratorStateEnum; typedef struct JSAsyncGeneratorRequest { struct list_head link; /* completion */ int completion_type; /* GEN_MAGIC_x */ JSValue result; /* promise capability */ JSValue promise; JSValue resolving_funcs[2]; } JSAsyncGeneratorRequest; typedef struct JSAsyncGeneratorData { JSObject *generator; /* back pointer to the object (const) */ JSAsyncGeneratorStateEnum state; JSAsyncFunctionState func_state; struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ } JSAsyncGeneratorData; static void js_async_generator_free(JSRuntime *rt, JSAsyncGeneratorData *s) { struct list_head *el, *el1; JSAsyncGeneratorRequest *req; list_for_each_safe(el, el1, &s->queue) { req = list_entry(el, JSAsyncGeneratorRequest, link); JS_FreeValueRT(rt, req->result); JS_FreeValueRT(rt, req->promise); JS_FreeValueRT(rt, req->resolving_funcs[0]); JS_FreeValueRT(rt, req->resolving_funcs[1]); js_free_rt(rt, req); } if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { async_func_free(rt, &s->func_state); } js_free_rt(rt, s); } static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj) { JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); if (s) { js_async_generator_free(rt, s); } } static void js_async_generator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); struct list_head *el; JSAsyncGeneratorRequest *req; if (s) { list_for_each(el, &s->queue) { req = list_entry(el, JSAsyncGeneratorRequest, link); JS_MarkValue(rt, req->result, mark_func); JS_MarkValue(rt, req->promise, mark_func); JS_MarkValue(rt, req->resolving_funcs[0], mark_func); JS_MarkValue(rt, req->resolving_funcs[1], mark_func); } if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { async_func_mark(rt, &s->func_state, mark_func); } } } static JSValue js_async_generator_resolve_function(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int magic, JSValue *func_data); static int js_async_generator_resolve_function_create(JSContext *ctx, JSValueConst generator, JSValue *resolving_funcs, BOOL is_resume_next) { int i; JSValue func; for(i = 0; i < 2; i++) { func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, i + is_resume_next * 2, 1, &generator); if (JS_IsException(func)) { if (i == 1) JS_FreeValue(ctx, resolving_funcs[0]); return -1; } resolving_funcs[i] = func; } return 0; } static int js_async_generator_await(JSContext *ctx, JSAsyncGeneratorData *s, JSValueConst value) { JSValue promise, resolving_funcs[2], resolving_funcs1[2]; int i, res; promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value, 0); if (JS_IsException(promise)) goto fail; if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), resolving_funcs, FALSE)) { JS_FreeValue(ctx, promise); goto fail; } /* Note: no need to create 'thrownawayCapability' as in the spec */ for(i = 0; i < 2; i++) resolving_funcs1[i] = JS_UNDEFINED; res = perform_promise_then(ctx, promise, (JSValueConst *)resolving_funcs, (JSValueConst *)resolving_funcs1); JS_FreeValue(ctx, promise); for(i = 0; i < 2; i++) JS_FreeValue(ctx, resolving_funcs[i]); if (res) goto fail; return 0; fail: return -1; } static void js_async_generator_resolve_or_reject(JSContext *ctx, JSAsyncGeneratorData *s, JSValueConst result, int is_reject) { JSAsyncGeneratorRequest *next; JSValue ret; next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); list_del(&next->link); ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, &result); JS_FreeValue(ctx, ret); JS_FreeValue(ctx, next->result); JS_FreeValue(ctx, next->promise); JS_FreeValue(ctx, next->resolving_funcs[0]); JS_FreeValue(ctx, next->resolving_funcs[1]); js_free(ctx, next); } static void js_async_generator_resolve(JSContext *ctx, JSAsyncGeneratorData *s, JSValueConst value, BOOL done) { JSValue result; result = js_create_iterator_result(ctx, JS_DupValue(ctx, value), done); /* XXX: better exception handling ? */ js_async_generator_resolve_or_reject(ctx, s, result, 0); JS_FreeValue(ctx, result); } static void js_async_generator_reject(JSContext *ctx, JSAsyncGeneratorData *s, JSValueConst exception) { js_async_generator_resolve_or_reject(ctx, s, exception, 1); } static void js_async_generator_complete(JSContext *ctx, JSAsyncGeneratorData *s) { if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; async_func_free(ctx->rt, &s->func_state); } } static int js_async_generator_completed_return(JSContext *ctx, JSAsyncGeneratorData *s, JSValueConst value) { JSValue promise, resolving_funcs[2], resolving_funcs1[2]; int res; promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&value, 0); if (JS_IsException(promise)) return -1; if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), resolving_funcs1, TRUE)) { JS_FreeValue(ctx, promise); return -1; } resolving_funcs[0] = JS_UNDEFINED; resolving_funcs[1] = JS_UNDEFINED; res = perform_promise_then(ctx, promise, (JSValueConst *)resolving_funcs1, (JSValueConst *)resolving_funcs); JS_FreeValue(ctx, resolving_funcs1[0]); JS_FreeValue(ctx, resolving_funcs1[1]); JS_FreeValue(ctx, promise); return res; } static void js_async_generator_resume_next(JSContext *ctx, JSAsyncGeneratorData *s) { JSAsyncGeneratorRequest *next; JSValue func_ret, value; for(;;) { if (list_empty(&s->queue)) break; next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); switch(s->state) { case JS_ASYNC_GENERATOR_STATE_EXECUTING: /* only happens when restarting execution after await() */ goto resume_exec; case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: goto done; case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: if (next->completion_type == GEN_MAGIC_NEXT) { goto exec_no_arg; } else { js_async_generator_complete(ctx, s); } break; case JS_ASYNC_GENERATOR_STATE_COMPLETED: if (next->completion_type == GEN_MAGIC_NEXT) { js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE); } else if (next->completion_type == GEN_MAGIC_RETURN) { s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; js_async_generator_completed_return(ctx, s, next->result); goto done; } else { js_async_generator_reject(ctx, s, next->result); } goto done; case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: value = JS_DupValue(ctx, next->result); if (next->completion_type == GEN_MAGIC_THROW && s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { JS_Throw(ctx, value); s->func_state.throw_flag = TRUE; } else { /* 'yield' returns a value. 'yield *' also returns a value in case the 'throw' method is called */ s->func_state.frame.cur_sp[-1] = value; s->func_state.frame.cur_sp[0] = JS_NewInt32(ctx, next->completion_type); s->func_state.frame.cur_sp++; exec_no_arg: s->func_state.throw_flag = FALSE; } s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; resume_exec: func_ret = async_func_resume(ctx, &s->func_state); if (JS_IsException(func_ret)) { value = JS_GetException(ctx); js_async_generator_complete(ctx, s); js_async_generator_reject(ctx, s, value); JS_FreeValue(ctx, value); } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { int func_ret_code; value = s->func_state.frame.cur_sp[-1]; s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; func_ret_code = JS_VALUE_GET_INT(func_ret); switch(func_ret_code) { case FUNC_RET_YIELD: case FUNC_RET_YIELD_STAR: if (func_ret_code == FUNC_RET_YIELD_STAR) s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; else s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; js_async_generator_resolve(ctx, s, value, FALSE); JS_FreeValue(ctx, value); break; case FUNC_RET_AWAIT: js_async_generator_await(ctx, s, value); JS_FreeValue(ctx, value); goto done; default: abort(); } } else { assert(JS_IsUndefined(func_ret)); /* end of function */ value = s->func_state.frame.cur_sp[-1]; s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; js_async_generator_complete(ctx, s); js_async_generator_resolve(ctx, s, value, TRUE); JS_FreeValue(ctx, value); } break; default: abort(); } } done: ; } static JSValue js_async_generator_resolve_function(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int magic, JSValue *func_data) { BOOL is_reject = magic & 1; JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); JSValueConst arg = argv[0]; /* XXX: what if s == NULL */ if (magic >= 2) { /* resume next case in AWAITING_RETURN state */ assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; if (is_reject) { js_async_generator_reject(ctx, s, arg); } else { js_async_generator_resolve(ctx, s, arg, TRUE); } } else { /* restart function execution after await() */ assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); s->func_state.throw_flag = is_reject; if (is_reject) { JS_Throw(ctx, JS_DupValue(ctx, arg)); } else { /* return value of await */ s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg); } js_async_generator_resume_next(ctx, s); } return JS_UNDEFINED; } /* magic = GEN_MAGIC_x */ static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); JSValue promise, resolving_funcs[2]; JSAsyncGeneratorRequest *req; promise = JS_NewPromiseCapability(ctx, resolving_funcs); if (JS_IsException(promise)) return JS_EXCEPTION; if (!s) { JSValue err, res2; JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); err = JS_GetException(ctx); res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&err); JS_FreeValue(ctx, err); JS_FreeValue(ctx, res2); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); return promise; } req = js_mallocz(ctx, sizeof(*req)); if (!req) goto fail; req->completion_type = magic; req->result = JS_DupValue(ctx, argv[0]); req->promise = JS_DupValue(ctx, promise); req->resolving_funcs[0] = resolving_funcs[0]; req->resolving_funcs[1] = resolving_funcs[1]; list_add_tail(&req->link, &s->queue); if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { js_async_generator_resume_next(ctx, s); } return promise; fail: JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); JS_FreeValue(ctx, promise); return JS_EXCEPTION; } static JSValue js_async_generator_function_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSValue obj, func_ret; JSAsyncGeneratorData *s; s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; init_list_head(&s->queue); if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; goto fail; } /* execute the function up to 'OP_initial_yield' (no yield nor await are possible) */ func_ret = async_func_resume(ctx, &s->func_state); if (JS_IsException(func_ret)) goto fail; JS_FreeValue(ctx, func_ret); obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); if (JS_IsException(obj)) goto fail; s->generator = JS_VALUE_GET_OBJ(obj); JS_SetOpaque(obj, s); return obj; fail: js_async_generator_free(ctx->rt, s); return JS_EXCEPTION; } /* 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, #ifdef CONFIG_BIGNUM TOK_MATH_POW_ASSIGN, #endif TOK_POW_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, #ifdef CONFIG_BIGNUM TOK_MATH_POW, #endif 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_VAR, TOK_THIS, TOK_DELETE, TOK_VOID, TOK_TYPEOF, TOK_NEW, TOK_IN, TOK_INSTANCEOF, 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_PACKAGE, 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; int has_iterator; } BlockEnv; typedef struct JSHoistedDef { int cpool_idx; /* -1 means variable global definition */ uint8_t force_init : 1; /* initialize to undefined */ uint8_t is_lexical : 1; /* global let/const definition */ uint8_t is_const : 1; /* const definition */ int var_idx; /* function object index if cpool_idx >= 0 */ int scope_level; /* scope of definition */ JSAtom var_name; /* variable name if cpool_idx < 0 */ } JSHoistedDef; 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; int line_num; } LineNumberSlot; 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_CONSTRUCTOR, JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, } JSParseFunctionEnum; typedef enum JSParseExportEnum { JS_PARSE_EXPORT_NONE, JS_PARSE_EXPORT_NAMED, JS_PARSE_EXPORT_DEFAULT, } JSParseExportEnum; 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_home_object; /* TRUE if the home object is available */ BOOL has_prototype; /* true if a prototype field is necessary */ BOOL has_simple_parameter_list; BOOL has_use_strict; /* to reject directive in special cases */ BOOL has_eval_call; /* true if the function contains a call to eval() */ BOOL has_arguments_binding; /* true if the 'arguments' binding is available in the function */ BOOL has_this_binding; /* true if the 'this' and new.target binding are available in the function */ BOOL new_target_allowed; /* true if the 'new.target' does not throw a syntax error */ BOOL super_call_allowed; /* true if super() is allowed */ BOOL super_allowed; /* true if super. or super[] is allowed */ BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */ BOOL is_derived_class_constructor; BOOL in_function_body; BOOL backtrace_barrier; JSFunctionKindEnum func_kind : 8; JSParseFunctionEnum func_type : 8; uint8_t js_mode; /* bitmap of JS_MODE_x */ JSAtom func_name; /* JS_ATOM_NULL if no name */ JSVarDef *vars; int var_size; /* allocated size for vars[] */ int var_count; JSVarDef *args; int arg_size; /* allocated size for args[] */ int arg_count; /* number of arguments */ int defined_arg_count; int var_object_idx; /* -1 if none */ int arguments_var_idx; /* -1 if none */ int func_var_idx; /* variable containing the current function (-1 if none, only used if is_func_expr is true) */ int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ int this_var_idx; /* variable containg the 'this' value, -1 if none */ int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ int home_object_var_idx; BOOL need_home_object; int scope_level; /* index into fd->scopes if the current lexical scope */ int scope_first; /* index into vd->vars of first lexically scoped variable */ int scope_size; /* allocated size of fd->scopes array */ int scope_count; /* number of entries used in the fd->scopes array */ JSVarScope *scopes; JSVarScope def_scope_array[4]; int hoisted_def_count; int hoisted_def_size; JSHoistedDef *hoisted_def; DynBuf byte_code; int last_opcode_pos; /* -1 if no last opcode */ int last_opcode_line_num; BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ LabelSlot *label_slots; int label_size; /* allocated size for label_slots[] */ int label_count; BlockEnv *top_break; /* break/continue label stack */ /* constant pool (strings, functions, numbers) */ JSValue *cpool; uint32_t cpool_count; uint32_t cpool_size; /* list of variables in the closure */ int closure_var_count; int closure_var_size; JSClosureVar *closure_var; JumpSlot *jump_slots; int jump_size; int jump_count; LineNumberSlot *line_number_slots; int line_number_size; int line_number_count; int line_number_last; int line_number_last_pc; /* pc2line table */ JSAtom filename; int line_num; DynBuf pc2line; char *source; /* raw source, utf-8 encoded */ int source_len; JSModuleDef *module; /* != NULL when parsing a module */ } JSFunctionDef; typedef struct JSToken { int val; int line_num; /* line number of token start */ const uint8_t *ptr; union { struct { JSValue str; int sep; } str; struct { JSValue val; #ifdef CONFIG_BIGNUM slimb_t exponent; /* may be != 0 only if val is a float */ #endif } num; struct { JSAtom atom; BOOL has_escape; BOOL is_reserved; } ident; struct { JSValue body; JSValue flags; } regexp; } u; } JSToken; typedef struct JSParseState { JSContext *ctx; int last_line_num; /* line number of last token */ int line_num; /* line number of current offset */ 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_ptr; const uint8_t *buf_end; /* current function code */ JSFunctionDef *cur_func; BOOL is_module; /* parsing a module */ BOOL allow_html_comments; } 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) { #ifdef CONFIG_BIGNUM case TOK_NUMBER: JS_FreeValue(s->ctx, token->u.num.val); break; #endif 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: case TOK_FIRST_KEYWORD ... TOK_LAST_KEYWORD: case TOK_PRIVATE_NAME: JS_FreeAtom(s->ctx, token->u.ident.atom); break; default: 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; } } int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...) { JSContext *ctx = s->ctx; va_list ap; int backtrace_flags; va_start(ap, fmt); JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); va_end(ap); backtrace_flags = 0; if (s->cur_func && s->cur_func->backtrace_barrier) backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; build_backtrace(ctx, ctx->current_exception, s->filename, s->line_num, backtrace_flags); return -1; } 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; /* 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 == '\n') { s->line_num++; } else 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(s, "invalid UTF-8 sequence"); goto fail; } p = p_next; } if (string_buffer_putc(b, c)) goto fail; } s->token.val = TOK_TEMPLATE; s->token.u.str.sep = c; s->token.u.str.str = string_buffer_end(b); 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; /* 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 (!s->cur_func) { if (do_throw) js_parse_error(s, "invalid character in a JSON string"); goto fail; } 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 == '\\') { c = *p; 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++; if (sep != '`') s->line_num++; continue; default: if (c >= '0' && c <= '7') { if (!s->cur_func) goto invalid_octal; /* JSON case */ if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`') goto parse_escape; if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { p++; c = '\0'; } else { invalid_octal: if (do_throw) js_parse_error(s, "invalid octal syntax 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) { if (do_throw) js_parse_error(s, "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; } token->val = TOK_STRING; token->u.str.sep = c; token->u.str.str = string_buffer_end(b); *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; 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(s, "invalid UTF-8 sequence"); goto fail; } p = p_next; /* LS or PS are considered as line terminator */ if (c == CP_LS || c == CP_PS) { eol_error: js_parse_error(s, "unexpected line terminator in regexp"); goto fail; } } 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) { goto invalid_utf8; } } if (!lre_js_is_ident_next(c)) break; if (string_buffer_putc(b2, c)) goto fail; p = p_next; } s->token.val = TOK_REGEXP; s->token.u.regexp.body = string_buffer_end(b); s->token.u.regexp.flags = string_buffer_end(b2); s->buf_ptr = p; return 0; fail: string_buffer_free(b); string_buffer_free(b2); return -1; } static __exception int next_token(JSParseState *s) { const uint8_t *p; int c; char buf[4096], *q; BOOL ident_has_escape; if (js_check_stack_overflow(s->ctx, 0)) { return js_parse_error(s, "stack overflow"); } free_token(s, &s->token); p = s->last_ptr = s->buf_ptr; s->got_lf = FALSE; s->last_line_num = s->token.line_num; redo: s->token.line_num = s->line_num; s->token.ptr = p; c = *p; switch(c) { case 0: s->token.val = TOK_EOF; break; case '`': if (!s->cur_func) { /* JSON does not accept templates */ goto def_token; } if (js_parse_template_part(s, p + 1)) goto fail; p = s->buf_ptr; break; case '\'': if (!s->cur_func) { /* JSON does not accept single quoted strings */ goto def_token; } /* fall through */ 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; s->line_num++; goto redo; case '\f': case '\v': if (!s->cur_func) { /* JSONWhitespace does not match , nor */ goto def_token; } /* fall through */ 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') { s->line_num++; s->got_lf = TRUE; /* considered as LF for ASI */ p++; } else if (*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 { 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 { 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' ... 'z': case 'A' ... 'Z': case '_': case '$': /* identifier */ p++; ident_has_escape = FALSE; has_ident: q = buf; for(;;) { const uint8_t *p1 = p; if (c < 128) { *q++ = c; } else { q += unicode_to_utf8((uint8_t*)q, c); } c = *p1++; if (c == '\\' && *p1 == 'u') { c = lre_parse_escape(&p1, TRUE); ident_has_escape = TRUE; } else if (c >= 128) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); } /* XXX: check if c >= 0 and c <= 0x10FFFF */ if (!lre_js_is_ident_next(c)) break; p = p1; if ((q - buf) >= sizeof(buf) - UTF8_CHAR_LEN_MAX) { js_parse_error(s, "identifier too long"); goto fail; } } *q = '\0'; s->token.u.ident.atom = JS_NewAtomLen(s->ctx, buf, q - buf); s->token.u.ident.has_escape = ident_has_escape; s->token.u.ident.is_reserved = FALSE; if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && s->cur_func && (s->cur_func->js_mode & JS_MODE_STRICT)) || (s->token.u.ident.atom == JS_ATOM_yield && s->cur_func && ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && !s->cur_func->in_function_body && s->cur_func->parent && (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || (s->token.u.ident.atom == JS_ATOM_await && (s->is_module || (s->cur_func && ((s->cur_func->func_kind & JS_FUNC_ASYNC) || (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && !s->cur_func->in_function_body && s->cur_func->parent && (s->cur_func->parent->func_kind & JS_FUNC_ASYNC))))))) { if (ident_has_escape) { s->token.u.ident.is_reserved = TRUE; s->token.val = TOK_IDENT; } else { /* The keywords atoms are pre allocated */ s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; } } else { s->token.val = TOK_IDENT; } break; case '#': /* private name */ { const uint8_t *p1; p++; q = buf; *q++ = '#'; p1 = p; c = *p1++; if (c == '\\' && *p1 == 'u') { c = lre_parse_escape(&p1, TRUE); } else if (c >= 128) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); } if (!lre_js_is_ident_first(c)) { js_parse_error(s, "invalid first character of private name"); goto fail; } p = p1; for(;;) { if (c < 128) { *q++ = c; } else { q += unicode_to_utf8((uint8_t*)q, c); } p1 = p; c = *p1++; if (c == '\\' && *p1 == 'u') { c = lre_parse_escape(&p1, TRUE); ident_has_escape = TRUE; } else if (c >= 128) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); } /* XXX: check if c >= 0 and c <= 0x10FFFF */ if (!lre_js_is_ident_next(c)) break; p = p1; if ((q - buf) >= sizeof(buf) - UTF8_CHAR_LEN_MAX) { js_parse_error(s, "private name too long"); goto fail; } } *q = '\0'; s->token.u.ident.atom = JS_NewAtomLen(s->ctx, buf, q - buf); s->token.val = TOK_PRIVATE_NAME; } 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 or JSON parsing mode, octal literals are not accepted */ if (is_digit(p[1]) && (!s->cur_func || (s->cur_func->js_mode & JS_MODE_STRICT))) { js_parse_error(s, "octal literals are deprecated in strict mode"); goto fail; } goto parse_number; case '1' ... '9': /* number */ parse_number: { JSValue ret; const uint8_t *p1; int flags, radix; if (!s->cur_func) { flags = 0; radix = 10; } else { flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | ATOD_ACCEPT_UNDERSCORES; #ifdef CONFIG_BIGNUM flags |= ATOD_ACCEPT_SUFFIX; if (s->cur_func->js_mode & JS_MODE_MATH) { flags |= ATOD_MODE_BIGINT; if (s->cur_func->js_mode & JS_MODE_MATH) flags |= ATOD_TYPE_BIG_FLOAT; } #endif radix = 0; } #ifdef CONFIG_BIGNUM s->token.u.num.exponent = 0; ret = js_atof2(s->ctx, (const char *)p, (const char **)&p, radix, flags, &s->token.u.num.exponent); #else ret = js_atof(s->ctx, (const char *)p, (const char **)&p, radix, flags); #endif if (JS_IsException(ret)) goto fail; /* reject `10instanceof Number` */ 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] == '-') { if (s->allow_html_comments && p[2] == '>' && s->last_line_num != s->line_num) { /* Annex B: `-->` at beginning of line is an html comment end. It extends to the end of the line. */ goto skip_line_comment; } 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 if (s->allow_html_comments && p[1] == '!' && p[2] == '-' && p[3] == '-') { /* Annex B: handle `" is not considered as an HTML comment. It is necessary because in the spec the function body is parsed separately. */ /* XXX: find a simpler way or be deliberately incompatible to simplify the code ? */ func_start_pos = b->len - 1; /* the leading '(' is not in the source */ 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 (patch_function_constructor_source(ctx, obj, func_start_pos) < 0) goto fail1; if (!JS_IsUndefined(new_target)) { /* set the prototype */ proto = js_get_prototype_from_ctor(ctx, new_target, JS_UNDEFINED); if (JS_IsException(proto)) goto fail1; if (!JS_IsUndefined(proto)) { ret = JS_SetPrototypeInternal(ctx, obj, proto, TRUE); 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 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; 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_length32(ctx, &len, array_arg)) return NULL; /* 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->class_id == JS_CLASS_ARGUMENTS) && 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; } 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_UNDEFINED || JS_VALUE_GET_TAG(array_arg) == JS_TAG_NULL) { return JS_Call(ctx, this_val, this_arg, 0, NULL); } tab = build_arg_list(ctx, &len, array_arg); if (!tab) return JS_EXCEPTION; if (magic) { ret = JS_CallConstructor2(ctx, this_val, this_arg, len, (JSValueConst *)tab); } else { ret = JS_Call(ctx, this_val, this_arg, len, (JSValueConst *)tab); } free_arg_list(ctx, tab, len); return ret; } static JSValue js_function_call(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc <= 0) { return JS_Call(ctx, this_val, JS_UNDEFINED, 0, NULL); } else { return JS_Call(ctx, this_val, argv[0], argc - 1, argv + 1); } } static JSValue js_function_bind(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSBoundFunction *bf; JSValue func_obj, name1; JSObject *p; int arg_count, i; uint32_t len1; if (check_function(ctx, this_val)) return JS_EXCEPTION; func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, JS_CLASS_BOUND_FUNCTION); if (JS_IsException(func_obj)) return JS_EXCEPTION; p = JS_VALUE_GET_OBJ(func_obj); p->is_constructor = JS_IsConstructor(ctx, this_val); arg_count = max_int(0, argc - 1); bf = js_malloc(ctx, sizeof(*bf) + arg_count * sizeof(JSValue)); if (!bf) goto exception; bf->func_obj = JS_DupValue(ctx, this_val); bf->this_val = JS_DupValue(ctx, argv[0]); bf->argc = arg_count; for(i = 0; i < arg_count; i++) { bf->argv[i] = JS_DupValue(ctx, argv[i + 1]); } p->u.bound_function = bf; name1 = JS_GetProperty(ctx, this_val, JS_ATOM_name); if (JS_IsException(name1)) goto exception; if (!JS_IsString(name1)) { JS_FreeValue(ctx, name1); name1 = JS_AtomToString(ctx, JS_ATOM_empty_string); } name1 = JS_ConcatString3(ctx, "bound ", name1, ""); if (JS_IsException(name1)) goto exception; JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name1, JS_PROP_CONFIGURABLE); if (js_get_length32(ctx, &len1, this_val)) goto exception; if (len1 <= (uint32_t)arg_count) len1 = 0; else len1 -= arg_count; JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, JS_NewUint32(ctx, len1), JS_PROP_CONFIGURABLE); return func_obj; exception: JS_FreeValue(ctx, func_obj); return JS_EXCEPTION; } static JSValue js_function_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSObject *p; JSFunctionKindEnum func_kind = JS_FUNC_NORMAL; if (check_function(ctx, this_val)) return JS_EXCEPTION; p = JS_VALUE_GET_OBJ(this_val); 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); } func_kind = b->func_kind; } { JSValue name; const char *pref, *suff; if (p->is_class) { pref = "class "; suff = " {\n [native code]\n}"; } else { switch(func_kind) { default: case JS_FUNC_NORMAL: pref = "function "; break; case JS_FUNC_GENERATOR: pref = "function *"; break; case JS_FUNC_ASYNC: pref = "async function "; break; case JS_FUNC_ASYNC_GENERATOR: pref = "async function *"; break; } suff = "() {\n [native code]\n}"; } name = JS_GetProperty(ctx, this_val, JS_ATOM_name); if (JS_IsUndefined(name)) name = JS_AtomToString(ctx, JS_ATOM_empty_string); return JS_ConcatString3(ctx, pref, name, suff); } } static JSValue js_function_hasInstance(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int ret; ret = JS_OrdinaryIsInstanceOf(ctx, argv[0], this_val); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static const JSCFunctionListEntry js_function_proto_funcs[] = { JS_CFUNC_DEF("call", 1, js_function_call ), JS_CFUNC_MAGIC_DEF("apply", 2, js_function_apply, 0 ), JS_CFUNC_DEF("bind", 1, js_function_bind ), JS_CFUNC_DEF("toString", 0, js_function_toString ), JS_CFUNC_DEF("[Symbol.hasInstance]", 1, js_function_hasInstance ), JS_CGETSET_DEF("fileName", js_function_proto_fileName, NULL ), JS_CGETSET_DEF("lineNumber", js_function_proto_lineNumber, NULL ), }; /* Error class */ static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int magic) { JSValue obj, msg, proto; JSValueConst proto1; if (JS_IsUndefined(new_target)) new_target = JS_GetActiveFunction(ctx); if (magic < 0) { proto1 = ctx->class_proto[JS_CLASS_ERROR]; } else { proto1 = ctx->native_error_proto[magic]; } proto = js_get_prototype_from_ctor(ctx, new_target, proto1); if (JS_IsException(proto)) return proto; obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_ERROR); JS_FreeValue(ctx, proto); if (JS_IsException(obj)) return obj; if (!JS_IsUndefined(argv[0])) { msg = JS_ToString(ctx, argv[0]); if (unlikely(JS_IsException(msg))) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } /* skip the Error() function in the backtrace */ build_backtrace(ctx, obj, NULL, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); return obj; } 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_IsUndefined(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_IsUndefined(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) { int64_t i, from, to; JSValue val; int fromPresent; /* XXX: should special case fast arrays */ for (i = 0; i < count; i++) { if (dir < 0) { from = from_pos + count - i - 1; to = to_pos + count - i - 1; } else { from = from_pos + i; to = to_pos + i; } 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; } } 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]))) 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_array_from(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // from(items, mapfn = void 0, this_arg = void 0) JSValueConst items = argv[0], mapfn, this_arg; JSValueConst args[2]; JSValue stack[2]; JSValue iter, r, v, v2, arrayLike; int64_t k, len; int done, mapping; mapping = FALSE; mapfn = JS_UNDEFINED; this_arg = JS_UNDEFINED; r = JS_UNDEFINED; arrayLike = JS_UNDEFINED; stack[0] = JS_UNDEFINED; stack[1] = JS_UNDEFINED; if (argc > 1) { mapfn = argv[1]; if (!JS_IsUndefined(mapfn)) { if (check_function(ctx, mapfn)) goto exception; mapping = 1; if (argc > 2) this_arg = argv[2]; } } iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator); if (JS_IsException(iter)) goto exception; if (!JS_IsUndefined(iter)) { JS_FreeValue(ctx, iter); if (JS_IsConstructor(ctx, this_val)) r = JS_CallConstructor(ctx, this_val, 0, NULL); else r = JS_NewArray(ctx); if (JS_IsException(r)) goto exception; stack[0] = JS_DupValue(ctx, items); if (js_for_of_start(ctx, &stack[1], FALSE)) goto exception; for (k = 0;; k++) { v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done); if (JS_IsException(v)) goto exception_close; if (done) break; if (mapping) { args[0] = v; args[1] = JS_NewInt32(ctx, k); v2 = JS_Call(ctx, mapfn, this_arg, 2, args); JS_FreeValue(ctx, v); v = v2; if (JS_IsException(v)) goto exception_close; } if (JS_DefinePropertyValueInt64(ctx, r, k, v, JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception_close; } } else { arrayLike = JS_ToObject(ctx, items); if (JS_IsException(arrayLike)) goto exception; if (js_get_length64(ctx, &len, arrayLike) < 0) goto exception; v = JS_NewInt64(ctx, len); args[0] = v; if (JS_IsConstructor(ctx, this_val)) { r = JS_CallConstructor(ctx, this_val, 1, args); } else { r = js_array_constructor(ctx, JS_UNDEFINED, 1, args); } JS_FreeValue(ctx, v); if (JS_IsException(r)) goto exception; for(k = 0; k < len; k++) { v = JS_GetPropertyInt64(ctx, arrayLike, k); if (JS_IsException(v)) goto exception; if (mapping) { args[0] = v; args[1] = JS_NewInt32(ctx, k); v2 = JS_Call(ctx, mapfn, this_arg, 2, args); JS_FreeValue(ctx, v); v = v2; if (JS_IsException(v)) goto exception; } if (JS_DefinePropertyValueInt64(ctx, r, k, v, JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception; } } if (JS_SetProperty(ctx, r, JS_ATOM_length, JS_NewUint32(ctx, k)) < 0) goto exception; goto done; exception_close: if (!JS_IsUndefined(stack[0])) JS_IteratorClose(ctx, stack[0], TRUE); exception: JS_FreeValue(ctx, r); r = JS_EXCEPTION; done: JS_FreeValue(ctx, arrayLike); JS_FreeValue(ctx, stack[0]); JS_FreeValue(ctx, stack[1]); return r; } static JSValue js_array_of(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, args[1]; int i; if (JS_IsConstructor(ctx, this_val)) { args[0] = JS_NewInt32(ctx, argc); obj = JS_CallConstructor(ctx, this_val, 1, (JSValueConst *)args); } else { obj = JS_NewArray(ctx); } if (JS_IsException(obj)) return JS_EXCEPTION; for(i = 0; i < argc; i++) { if (JS_CreateDataPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i]), JS_PROP_THROW) < 0) { goto fail; } } if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewUint32(ctx, argc)) < 0) { fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } return obj; } static JSValue js_array_isArray(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int ret; ret = JS_IsArray(ctx, argv[0]); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_get_this(JSContext *ctx, JSValueConst this_val) { return JS_DupValue(ctx, this_val); } static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, JSValueConst len_val) { JSValue ctor, ret; int res; res = JS_IsArray(ctx, obj); if (res < 0) return JS_EXCEPTION; if (!res) return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val); ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED); if (JS_IsException(ctor)) return JS_EXCEPTION; if (JS_IsUndefined(ctor)) return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val); ret = JS_CallConstructor(ctx, ctor, 1, &len_val); JS_FreeValue(ctx, ctor); return ret; } static const JSCFunctionListEntry js_array_funcs[] = { JS_CFUNC_DEF("isArray", 1, js_array_isArray ), JS_CFUNC_DEF("from", 1, js_array_from ), JS_CFUNC_DEF("of", 0, js_array_of ), JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), }; 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_IsUndefined(val)) return JS_ToBoolFree(ctx, val); return JS_IsArray(ctx, obj); } static JSValue js_array_concat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, arr, val; JSValueConst e; int64_t len, k, n; int i, res; arr = JS_UNDEFINED; obj = JS_ToObject(ctx, this_val); if (JS_IsException(obj)) goto exception; arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0)); if (JS_IsException(arr)) goto exception; n = 0; for (i = -1; i < argc; i++) { if (i < 0) e = obj; else e = argv[i]; res = JS_isConcatSpreadable(ctx, e); if (res < 0) goto exception; if (res) { if (js_get_length64(ctx, &len, e)) goto exception; if (n + len >= MAX_SAFE_INTEGER) { JS_ThrowTypeError(ctx, "Array loo long"); goto exception; } for (k = 0; k < len; k++, n++) { res = JS_TryGetPropertyInt64(ctx, e, k, &val); if (res < 0) goto exception; if (res) { if (JS_DefinePropertyValueInt64(ctx, arr, n, val, JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception; } } } else { if (n >= MAX_SAFE_INTEGER) { JS_ThrowTypeError(ctx, "Array loo long"); goto exception; } if (JS_DefinePropertyValueInt64(ctx, arr, n, JS_DupValue(ctx, e), JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception; n++; } } if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0) goto exception; JS_FreeValue(ctx, obj); return arr; exception: JS_FreeValue(ctx, arr); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } #define special_every 0 #define special_some 1 #define special_forEach 2 #define special_map 3 #define special_filter 4 #define special_TA 8 static int js_typed_array_get_length_internal(JSContext *ctx, JSValueConst obj); static JSValue js_typed_array___speciesCreate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue js_array_every(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int special) { JSValue obj, val, index_val, res, ret; JSValueConst args[3]; JSValueConst func, this_arg; int64_t len, k, n; int present; ret = JS_UNDEFINED; val = JS_UNDEFINED; if (special & special_TA) { obj = JS_DupValue(ctx, this_val); len = js_typed_array_get_length_internal(ctx, obj); if (len < 0) goto exception; } else { obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; } func = argv[0]; this_arg = JS_UNDEFINED; if (argc > 1) this_arg = argv[1]; if (check_function(ctx, func)) goto exception; switch (special) { case special_every: case special_every | special_TA: ret = JS_TRUE; break; case special_some: case special_some | special_TA: ret = JS_FALSE; break; case special_map: /* XXX: JS_ArraySpeciesCreate should take int64_t */ ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt64(ctx, len)); if (JS_IsException(ret)) goto exception; break; case special_filter: ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0)); if (JS_IsException(ret)) goto exception; break; case special_map | special_TA: args[0] = obj; args[1] = JS_NewInt32(ctx, len); ret = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args); if (JS_IsException(ret)) goto exception; break; case special_filter | special_TA: ret = JS_NewArray(ctx); if (JS_IsException(ret)) goto exception; break; } n = 0; for(k = 0; k < len; k++) { present = JS_TryGetPropertyInt64(ctx, obj, k, &val); if (present < 0) goto exception; if (present) { index_val = JS_NewInt64(ctx, k); if (JS_IsException(index_val)) goto exception; args[0] = val; args[1] = index_val; args[2] = obj; res = JS_Call(ctx, func, this_arg, 3, args); JS_FreeValue(ctx, index_val); if (JS_IsException(res)) goto exception; switch (special) { case special_every: case special_every | special_TA: if (!JS_ToBoolFree(ctx, res)) { ret = JS_FALSE; goto done; } break; case special_some: case special_some | special_TA: if (JS_ToBoolFree(ctx, res)) { ret = JS_TRUE; goto done; } break; case special_map: if (JS_DefinePropertyValueInt64(ctx, ret, k, res, JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception; break; case special_map | special_TA: if (JS_SetPropertyValue(ctx, ret, JS_NewInt32(ctx, k), res, JS_PROP_THROW) < 0) goto exception; break; case special_filter: case special_filter | special_TA: if (JS_ToBoolFree(ctx, res)) { if (JS_DefinePropertyValueInt64(ctx, ret, n++, JS_DupValue(ctx, val), JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception; } break; default: JS_FreeValue(ctx, res); break; } JS_FreeValue(ctx, val); val = JS_UNDEFINED; } } done: if (special == (special_filter | special_TA)) { JSValue arr; args[0] = obj; args[1] = JS_NewInt32(ctx, n); arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args); if (JS_IsException(arr)) goto exception; args[0] = ret; res = JS_Invoke(ctx, arr, JS_ATOM_set, 1, args); if (check_exception_free(ctx, res)) goto exception; JS_FreeValue(ctx, ret); ret = arr; } JS_FreeValue(ctx, val); JS_FreeValue(ctx, obj); return ret; exception: JS_FreeValue(ctx, ret); JS_FreeValue(ctx, val); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } #define special_reduce 0 #define special_reduceRight 1 static JSValue js_array_reduce(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int special) { JSValue obj, val, index_val, acc, acc1; JSValueConst args[4]; JSValueConst func; int64_t len, k, k1; int present; acc = JS_UNDEFINED; val = JS_UNDEFINED; if (special & special_TA) { obj = JS_DupValue(ctx, this_val); len = js_typed_array_get_length_internal(ctx, obj); if (len < 0) goto exception; } else { obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; } func = argv[0]; if (check_function(ctx, func)) goto exception; k = 0; if (argc > 1) { acc = JS_DupValue(ctx, argv[1]); } else { for(;;) { if (k >= len) { JS_ThrowTypeError(ctx, "empty array"); goto exception; } k1 = (special & special_reduceRight) ? len - k - 1 : k; k++; present = JS_TryGetPropertyInt64(ctx, obj, k1, &acc); if (present < 0) goto exception; if (present) break; } } for (; k < len; k++) { k1 = (special & special_reduceRight) ? len - k - 1 : k; present = JS_TryGetPropertyInt64(ctx, obj, k1, &val); if (present < 0) goto exception; if (present) { index_val = JS_NewInt64(ctx, k1); if (JS_IsException(index_val)) goto exception; args[0] = acc; args[1] = val; args[2] = index_val; args[3] = obj; acc1 = JS_Call(ctx, func, JS_UNDEFINED, 4, args); JS_FreeValue(ctx, index_val); JS_FreeValue(ctx, val); val = JS_UNDEFINED; if (JS_IsException(acc1)) goto exception; JS_FreeValue(ctx, acc); acc = acc1; } } JS_FreeValue(ctx, obj); return acc; exception: JS_FreeValue(ctx, acc); JS_FreeValue(ctx, val); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_fill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj; int64_t len, start, end; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; start = 0; if (argc > 1 && !JS_IsUndefined(argv[1])) { if (JS_ToInt64Clamp(ctx, &start, argv[1], 0, len, len)) goto exception; } end = len; if (argc > 2 && !JS_IsUndefined(argv[2])) { if (JS_ToInt64Clamp(ctx, &end, argv[2], 0, len, len)) goto exception; } /* XXX: should special case fast arrays */ while (start < end) { if (JS_SetPropertyInt64(ctx, obj, start, JS_DupValue(ctx, argv[0])) < 0) goto exception; start++; } return obj; exception: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, val; int64_t len, n, res; JSValue *arrp; uint32_t count; 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_indexOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, val; int64_t len, n, res; JSValue *arrp; uint32_t count; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; res = -1; 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_STRICT)) { res = n; goto done; } } } for (; n < len; n++) { int present = JS_TryGetPropertyInt64(ctx, obj, n, &val); if (present < 0) goto exception; if (present) { if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) { res = n; break; } } } } done: JS_FreeValue(ctx, obj); return JS_NewInt64(ctx, res); exception: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_lastIndexOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, val; int64_t len, n, res; int present; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; res = -1; if (len > 0) { n = len - 1; if (argc > 1) { if (JS_ToInt64Clamp(ctx, &n, argv[1], -1, len - 1, len)) goto exception; } /* XXX: should special case fast arrays */ for (; n >= 0; n--) { present = JS_TryGetPropertyInt64(ctx, obj, n, &val); if (present < 0) goto exception; if (present) { if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) { res = n; break; } } } } JS_FreeValue(ctx, obj); return JS_NewInt64(ctx, res); exception: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_find(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int findIndex) { JSValueConst func, this_arg; JSValueConst args[3]; JSValue obj, val, index_val, res; int64_t len, k; index_val = JS_UNDEFINED; val = JS_UNDEFINED; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; func = argv[0]; if (check_function(ctx, func)) goto exception; this_arg = JS_UNDEFINED; if (argc > 1) this_arg = argv[1]; for(k = 0; k < len; k++) { index_val = JS_NewInt64(ctx, k); if (JS_IsException(index_val)) goto exception; val = JS_GetPropertyValue(ctx, obj, index_val); if (JS_IsException(val)) goto exception; args[0] = val; args[1] = index_val; args[2] = this_val; res = JS_Call(ctx, func, this_arg, 3, args); if (JS_IsException(res)) goto exception; if (JS_ToBoolFree(ctx, res)) { if (findIndex) { JS_FreeValue(ctx, val); JS_FreeValue(ctx, obj); return index_val; } else { JS_FreeValue(ctx, index_val); JS_FreeValue(ctx, obj); return val; } } JS_FreeValue(ctx, val); JS_FreeValue(ctx, index_val); } JS_FreeValue(ctx, obj); if (findIndex) return JS_NewInt32(ctx, -1); else return JS_UNDEFINED; exception: JS_FreeValue(ctx, index_val); JS_FreeValue(ctx, val); 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_join(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int toLocaleString) { JSValue obj, sep = JS_UNDEFINED, el; StringBuffer b_s, *b = &b_s; JSString *p = NULL; int64_t i, n; int c; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &n, obj)) goto exception; c = ','; /* default separator */ if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) { sep = JS_ToString(ctx, argv[0]); if (JS_IsException(sep)) goto exception; p = JS_VALUE_GET_STRING(sep); if (p->len == 1 && !p->is_wide_char) c = p->u.str8[0]; else c = -1; } string_buffer_init(ctx, b, 0); for(i = 0; i < n; i++) { if (i > 0) { if (c >= 0) { string_buffer_putc8(b, c); } else { string_buffer_concat(b, p, 0, p->len); } } el = JS_GetPropertyUint32(ctx, obj, i); if (JS_IsException(el)) goto fail; if (!JS_IsNull(el) && !JS_IsUndefined(el)) { if (toLocaleString) { el = JS_ToLocaleStringFree(ctx, el); } if (string_buffer_concat_value_free(b, el)) goto fail; } } JS_FreeValue(ctx, sep); JS_FreeValue(ctx, obj); return string_buffer_end(b); fail: string_buffer_free(b); JS_FreeValue(ctx, sep); exception: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_array_pop(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int shift) { JSValue obj, res = JS_UNDEFINED; 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_reverse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, lval, hval; JSValue *arrp; int64_t len, l, h; int l_present, h_present; uint32_t count32; lval = JS_UNDEFINED; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; /* Special case fast arrays */ if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { uint32_t ll, hh; if (count32 > 1) { for (ll = 0, hh = count32 - 1; ll < hh; ll++, hh--) { lval = arrp[ll]; arrp[ll] = arrp[hh]; arrp[hh] = lval; } } return obj; } for (l = 0, h = len - 1; l < h; l++, h--) { l_present = JS_TryGetPropertyInt64(ctx, obj, l, &lval); if (l_present < 0) goto exception; h_present = JS_TryGetPropertyInt64(ctx, obj, h, &hval); if (h_present < 0) goto exception; if (h_present) { if (JS_SetPropertyInt64(ctx, obj, l, hval) < 0) goto exception; if (l_present) { if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) { lval = JS_UNDEFINED; goto exception; } lval = JS_UNDEFINED; } else { if (JS_DeletePropertyInt64(ctx, obj, h, JS_PROP_THROW) < 0) goto exception; } } else { if (l_present) { if (JS_DeletePropertyInt64(ctx, obj, l, JS_PROP_THROW) < 0) goto exception; if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) { lval = JS_UNDEFINED; goto exception; } lval = JS_UNDEFINED; } } } return obj; exception: JS_FreeValue(ctx, lval); 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_UNDEFINED; 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_IsUndefined(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 JSValue js_array_copyWithin(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj; int64_t len, from, to, final, count; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; if (JS_ToInt64Clamp(ctx, &to, argv[0], 0, len, len)) goto exception; if (JS_ToInt64Clamp(ctx, &from, argv[1], 0, len, len)) goto exception; final = len; if (argc > 2 && !JS_IsUndefined(argv[2])) { if (JS_ToInt64Clamp(ctx, &final, argv[2], 0, len, len)) goto exception; } count = min_int64(final - from, len - to); if (JS_CopySubArray(ctx, obj, to, from, count, (from < to && to < from + count) ? -1 : +1)) goto exception; return obj; exception: JS_FreeValue(ctx, obj); 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; for (sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { present = JS_TryGetPropertyInt64(ctx, source, sourceIndex, &element); if (present < 0) return -1; if (!present) continue; if (!JS_IsUndefined(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_UNDEFINED, JS_UNDEFINED); 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_SetPropertyInt64(ctx, target, targetIndex, element) < 0) return -1; targetIndex++; } return targetIndex; fail: JS_FreeValue(ctx, element); return -1; } static JSValue js_array_flatten(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int map) { JSValue obj, arr; JSValueConst mapperFunction, thisArg; int64_t sourceLen; int depthNum; arr = JS_UNDEFINED; obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &sourceLen, obj)) goto exception; depthNum = 1; mapperFunction = JS_UNDEFINED; thisArg = JS_UNDEFINED; if (map) { mapperFunction = argv[0]; if (argc > 1) { thisArg = argv[1]; } if (check_function(ctx, mapperFunction)) goto exception; } else { if (argc > 0 && !JS_IsUndefined(argv[0])) { if (JS_ToInt32Sat(ctx, &depthNum, argv[0]) < 0) goto exception; } } arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0)); if (JS_IsException(arr)) goto exception; if (JS_FlattenIntoArray(ctx, arr, obj, sourceLen, 0, depthNum, mapperFunction, thisArg) < 0) goto exception; JS_FreeValue(ctx, obj); return arr; exception: JS_FreeValue(ctx, obj); JS_FreeValue(ctx, arr); return JS_EXCEPTION; } /* Array sort */ typedef struct ValueSlot { JSValue val; JSString *str; int64_t pos; } ValueSlot; struct array_sort_context { JSContext *ctx; int exception; int has_method; JSValueConst method; }; static int js_array_cmp_generic(const void *a, const void *b, void *opaque) { struct array_sort_context *psc = opaque; JSContext *ctx = psc->ctx; JSValueConst argv[2]; JSValue res; ValueSlot *ap = (ValueSlot *)(void *)a; ValueSlot *bp = (ValueSlot *)(void *)b; int cmp; if (psc->exception) return 0; if (psc->has_method) { /* custom sort function is specified as returning 0 for identical * objects: avoid method call overhead. */ if (!memcmp(&ap->val, &bp->val, sizeof(ap->val))) goto cmp_same; argv[0] = ap->val; argv[1] = bp->val; res = JS_Call(ctx, psc->method, JS_UNDEFINED, 2, argv); if (JS_IsException(res)) goto exception; if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) { int val = JS_VALUE_GET_INT(res); cmp = (val > 0) - (val < 0); } else { double val; if (JS_ToFloat64Free(ctx, &val, res) < 0) goto exception; cmp = (val > 0) - (val < 0); } } else { /* Not supposed to bypass ToString even for identical objects as * tested in test262/test/built-ins/Array/prototype/sort/bug_596_1.js */ if (!ap->str) { JSValue str = JS_ToString(ctx, ap->val); if (JS_IsException(str)) goto exception; ap->str = JS_VALUE_GET_STRING(str); } if (!bp->str) { JSValue str = JS_ToString(ctx, bp->val); if (JS_IsException(str)) goto exception; bp->str = JS_VALUE_GET_STRING(str); } cmp = js_string_compare(ctx, ap->str, bp->str); } if (cmp != 0) return cmp; cmp_same: /* make sort stable: compare array offsets */ return (ap->pos > bp->pos) - (ap->pos < bp->pos); exception: psc->exception = 1; return 0; } static JSValue js_array_sort(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { struct array_sort_context asc = { ctx, 0, 0, argv[0] }; JSValue obj = JS_UNDEFINED; ValueSlot *array = NULL; size_t array_size = 0, pos = 0, n = 0; int64_t i, len, undefined_count = 0; int present; if (!JS_IsUndefined(asc.method)) { if (check_function(ctx, asc.method)) goto exception; asc.has_method = 1; } obj = JS_ToObject(ctx, this_val); if (js_get_length64(ctx, &len, obj)) goto exception; /* XXX: should special case fast arrays */ for (i = 0; i < len; i++) { if (pos >= array_size) { size_t new_size, slack; ValueSlot *new_array; new_size = (array_size + (array_size >> 1) + 31) & ~15; new_array = js_realloc2(ctx, array, new_size * sizeof(*array), &slack); if (new_array == NULL) goto exception; new_size += slack / sizeof(*new_array); array = new_array; array_size = new_size; } present = JS_TryGetPropertyInt64(ctx, obj, i, &array[pos].val); if (present < 0) goto exception; if (present == 0) continue; if (JS_IsUndefined(array[pos].val)) { undefined_count++; continue; } array[pos].str = NULL; array[pos].pos = i; pos++; } rqsort(array, pos, sizeof(*array), js_array_cmp_generic, &asc); if (asc.exception) goto exception; /* XXX: should special case fast arrays */ while (n < pos) { if (array[n].str) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str)); if (array[n].pos == n) { JS_FreeValue(ctx, array[n].val); } else { if (JS_SetPropertyInt64(ctx, obj, n, array[n].val) < 0) { n++; goto exception; } } n++; } js_free(ctx, array); for (i = n; undefined_count-- > 0; i++) { if (JS_SetPropertyInt64(ctx, obj, i, JS_UNDEFINED) < 0) goto fail; } for (; i < len; i++) { if (JS_DeletePropertyInt64(ctx, obj, i, JS_PROP_THROW) < 0) goto fail; } return obj; exception: for (; n < pos; n++) { JS_FreeValue(ctx, array[n].val); if (array[n].str) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str)); } js_free(ctx, array); fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } typedef struct JSArrayIteratorData { JSValue obj; JSIteratorKindEnum kind; uint32_t idx; } JSArrayIteratorData; static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSArrayIteratorData *it = p->u.array_iterator_data; if (it) { JS_FreeValueRT(rt, it->obj); js_free_rt(rt, it); } } static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSArrayIteratorData *it = p->u.array_iterator_data; if (it) { JS_MarkValue(rt, it->obj, mark_func); } } 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 JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue enum_obj, arr; JSArrayIteratorData *it; JSIteratorKindEnum kind; int class_id; kind = magic & 3; if (magic & 4) { /* string iterator case */ arr = JS_ToStringCheckObject(ctx, this_val); class_id = JS_CLASS_STRING_ITERATOR; } else { arr = JS_ToObject(ctx, this_val); class_id = JS_CLASS_ARRAY_ITERATOR; } if (JS_IsException(arr)) goto fail; enum_obj = JS_NewObjectClass(ctx, class_id); if (JS_IsException(enum_obj)) goto fail; it = js_malloc(ctx, sizeof(*it)); if (!it) goto fail1; it->obj = arr; it->kind = kind; it->idx = 0; JS_SetOpaque(enum_obj, it); return enum_obj; fail1: JS_FreeValue(ctx, enum_obj); fail: JS_FreeValue(ctx, arr); return JS_EXCEPTION; } static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, BOOL *pdone, int magic) { JSArrayIteratorData *it; uint32_t len, idx; JSValue val, obj; JSObject *p; it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_ITERATOR); if (!it) goto fail1; if (JS_IsUndefined(it->obj)) goto done; p = JS_VALUE_GET_OBJ(it->obj); if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { if (typed_array_is_detached(ctx, p)) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail1; } len = p->u.array.count; } else { if (js_get_length32(ctx, &len, it->obj)) { fail1: *pdone = FALSE; return JS_EXCEPTION; } } idx = it->idx; if (idx >= len) { JS_FreeValue(ctx, it->obj); it->obj = JS_UNDEFINED; done: *pdone = TRUE; return JS_UNDEFINED; } it->idx = idx + 1; *pdone = FALSE; if (it->kind == JS_ITERATOR_KIND_KEY) { return JS_NewUint32(ctx, idx); } else { val = JS_GetPropertyUint32(ctx, it->obj, idx); if (JS_IsException(val)) return JS_EXCEPTION; if (it->kind == JS_ITERATOR_KIND_VALUE) { return val; } else { JSValueConst args[2]; JSValue num; num = JS_NewUint32(ctx, idx); args[0] = num; args[1] = val; obj = js_create_array(ctx, 2, args); JS_FreeValue(ctx, val); JS_FreeValue(ctx, num); return obj; } } } static JSValue js_iterator_proto_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_DupValue(ctx, this_val); } static const JSCFunctionListEntry js_iterator_proto_funcs[] = { JS_CFUNC_DEF("[Symbol.iterator]", 0, js_iterator_proto_iterator ), }; static const JSCFunctionListEntry js_array_proto_funcs[] = { JS_CFUNC_DEF("concat", 1, js_array_concat ), JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every ), JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some ), JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach ), JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map ), JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter ), JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce ), JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight ), JS_CFUNC_DEF("fill", 1, js_array_fill ), JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, 0 ), JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, 1 ), JS_CFUNC_DEF("indexOf", 1, js_array_indexOf ), JS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf ), JS_CFUNC_DEF("includes", 1, js_array_includes ), JS_CFUNC_MAGIC_DEF("join", 1, js_array_join, 0 ), JS_CFUNC_DEF("toString", 0, js_array_toString ), JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_array_join, 1 ), 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_DEF("reverse", 0, js_array_reverse ), JS_CFUNC_DEF("sort", 1, js_array_sort ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ), JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ), JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ), JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ), JS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0 ), JS_CFUNC_MAGIC_DEF("flatten", 0, js_array_flatten, 0 ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE ), JS_ALIAS_DEF("[Symbol.iterator]", "values" ), JS_CFUNC_MAGIC_DEF("keys", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY ), JS_CFUNC_MAGIC_DEF("entries", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ), }; static const JSCFunctionListEntry js_array_iterator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 0, js_array_iterator_next, 0 ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Array Iterator", JS_PROP_CONFIGURABLE ), }; /* Number */ static JSValue js_number_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue val, obj; if (argc == 0) { val = JS_NewInt32(ctx, 0); } else { val = JS_ToNumeric(ctx, argv[0]); if (JS_IsException(val)) return val; switch(JS_VALUE_GET_TAG(val)) { #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); double d; bf_get_float64(&p->num, &d, BF_RNDN); JS_FreeValue(ctx, val); val = __JS_NewFloat64(ctx, d); } break; case JS_TAG_BIG_DECIMAL: val = JS_ToStringFree(ctx, val); if (JS_IsException(val)) return val; val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) return val; break; #endif default: break; } } if (!JS_IsUndefined(new_target)) { obj = js_create_from_ctor(ctx, new_target, JS_CLASS_NUMBER); if (!JS_IsException(obj)) JS_SetObjectData(ctx, obj, val); return obj; } else { return val; } } #if 0 static JSValue js_number___toInteger(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ToIntegerFree(ctx, JS_DupValue(ctx, argv[0])); } static JSValue js_number___toLength(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int64_t v; if (JS_ToLengthFree(ctx, &v, JS_DupValue(ctx, argv[0]))) return JS_EXCEPTION; return JS_NewInt64(ctx, v); } #endif static JSValue js_number_isNaN(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (!JS_IsNumber(argv[0])) return JS_FALSE; return js_global_isNaN(ctx, this_val, argc, argv); } static JSValue js_number_isFinite(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (!JS_IsNumber(argv[0])) return JS_FALSE; return js_global_isFinite(ctx, this_val, argc, argv); } static JSValue js_number_isInteger(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int ret; ret = JS_NumberIsInteger(ctx, argv[0]); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_number_isSafeInteger(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; if (!JS_IsNumber(argv[0])) return JS_FALSE; if (unlikely(JS_ToFloat64(ctx, &d, argv[0]))) return JS_EXCEPTION; return JS_NewBool(ctx, is_safe_integer(d)); } static const JSCFunctionListEntry js_number_funcs[] = { /* global ParseInt and parseFloat should be defined already or delayed */ JS_ALIAS_BASE_DEF("parseInt", "parseInt", 0 ), JS_ALIAS_BASE_DEF("parseFloat", "parseFloat", 0 ), JS_CFUNC_DEF("isNaN", 1, js_number_isNaN ), JS_CFUNC_DEF("isFinite", 1, js_number_isFinite ), JS_CFUNC_DEF("isInteger", 1, js_number_isInteger ), JS_CFUNC_DEF("isSafeInteger", 1, js_number_isSafeInteger ), JS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0 ), JS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0 ), JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), JS_PROP_DOUBLE_DEF("NEGATIVE_INFINITY", -INFINITY, 0 ), JS_PROP_DOUBLE_DEF("POSITIVE_INFINITY", INFINITY, 0 ), JS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0 ), /* ES6 */ JS_PROP_DOUBLE_DEF("MAX_SAFE_INTEGER", 9007199254740991.0, 0 ), /* ES6 */ JS_PROP_DOUBLE_DEF("MIN_SAFE_INTEGER", -9007199254740991.0, 0 ), /* ES6 */ //JS_CFUNC_DEF("__toInteger", 1, js_number___toInteger ), //JS_CFUNC_DEF("__toLength", 1, js_number___toLength ), }; static JSValue js_thisNumberValue(JSContext *ctx, JSValueConst this_val) { if (JS_IsNumber(this_val)) 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_NUMBER) { if (JS_IsNumber(p->u.object_data)) return JS_DupValue(ctx, p->u.object_data); } } return JS_ThrowTypeError(ctx, "not a number"); } static JSValue js_number_valueOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_thisNumberValue(ctx, this_val); } static int js_get_radix(JSContext *ctx, JSValueConst val) { int radix; if (JS_ToInt32Sat(ctx, &radix, val)) return -1; if (radix < 2 || radix > 36) { JS_ThrowRangeError(ctx, "radix must be between 2 and 36"); return -1; } return radix; } static JSValue js_number_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue val; int base; double d; val = js_thisNumberValue(ctx, this_val); if (JS_IsException(val)) return val; if (magic || JS_IsUndefined(argv[0])) { base = 10; } else { base = js_get_radix(ctx, argv[0]); if (base < 0) goto fail; } if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; return js_dtoa(ctx, d, base, 0, JS_DTOA_VAR_FORMAT); fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_number_toFixed(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; int f; double d; val = js_thisNumberValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; if (JS_ToInt32Sat(ctx, &f, argv[0])) return JS_EXCEPTION; if (f < 0 || f > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); if (fabs(d) >= 1e21) { return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d)); } else { return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT); } } static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; int f, flags; double d; val = js_thisNumberValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; if (JS_ToInt32Sat(ctx, &f, argv[0])) return JS_EXCEPTION; if (!isfinite(d)) { return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d)); } if (JS_IsUndefined(argv[0])) { flags = 0; f = 0; } else { if (f < 0 || f > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); f++; flags = JS_DTOA_FIXED_FORMAT; } return js_dtoa(ctx, d, 10, f, flags | JS_DTOA_FORCE_EXP); } static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; int p; double d; val = js_thisNumberValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_ToFloat64Free(ctx, &d, val)) return JS_EXCEPTION; if (JS_IsUndefined(argv[0])) goto to_string; if (JS_ToInt32Sat(ctx, &p, argv[0])) return JS_EXCEPTION; if (!isfinite(d)) { to_string: return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d)); } if (p < 1 || p > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); return js_dtoa(ctx, d, 10, p, JS_DTOA_FIXED_FORMAT); } static const JSCFunctionListEntry js_number_proto_funcs[] = { JS_CFUNC_DEF("toExponential", 1, js_number_toExponential ), JS_CFUNC_DEF("toFixed", 1, js_number_toFixed ), JS_CFUNC_DEF("toPrecision", 1, js_number_toPrecision ), JS_CFUNC_MAGIC_DEF("toString", 1, js_number_toString, 0 ), JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_number_toString, 1 ), JS_CFUNC_DEF("valueOf", 0, js_number_valueOf ), }; static JSValue js_parseInt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *str, *p; int radix, flags; JSValue ret; str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; if (JS_ToInt32(ctx, &radix, argv[1])) { JS_FreeCString(ctx, str); return JS_EXCEPTION; } if (radix != 0 && (radix < 2 || radix > 36)) { ret = JS_NAN; } else { p = str; p += skip_spaces(p); flags = ATOD_INT_ONLY | ATOD_ACCEPT_PREFIX_AFTER_SIGN; ret = js_atof(ctx, p, NULL, radix, flags); } JS_FreeCString(ctx, str); return ret; } static JSValue js_parseFloat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *str, *p; JSValue ret; str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; p = str; p += skip_spaces(p); ret = js_atof(ctx, p, NULL, 10, 0); JS_FreeCString(ctx, str); return ret; } /* Boolean */ static JSValue js_boolean_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue val, obj; val = JS_NewBool(ctx, JS_ToBool(ctx, argv[0])); if (!JS_IsUndefined(new_target)) { obj = js_create_from_ctor(ctx, new_target, JS_CLASS_BOOLEAN); if (!JS_IsException(obj)) JS_SetObjectData(ctx, obj, val); return obj; } else { return val; } } static JSValue js_thisBooleanValue(JSContext *ctx, JSValueConst this_val) { if (JS_VALUE_GET_TAG(this_val) == JS_TAG_BOOL) 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_BOOLEAN) { if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_BOOL) return p->u.object_data; } } return JS_ThrowTypeError(ctx, "not a boolean"); } static JSValue js_boolean_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val = js_thisBooleanValue(ctx, this_val); if (JS_IsException(val)) return val; return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? JS_ATOM_true : JS_ATOM_false); } static JSValue js_boolean_valueOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_thisBooleanValue(ctx, this_val); } static const JSCFunctionListEntry js_boolean_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_boolean_toString ), JS_CFUNC_DEF("valueOf", 0, js_boolean_valueOf ), }; /* 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) { if (p1->is_wide_char) ch = p1->u.str16[idx]; else ch = p1->u.str8[idx]; desc->flags = JS_PROP_ENUMERABLE; desc->value = js_new_string_char(ctx, ch); desc->getter = JS_UNDEFINED; desc->setter = JS_UNDEFINED; } return TRUE; } } } return FALSE; } static uint32_t js_string_obj_get_length(JSContext *ctx, JSValueConst obj) { JSObject *p; JSString *p1; 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) { p1 = JS_VALUE_GET_STRING(p->u.object_data); len = p1->len; } return len; } static int js_string_get_own_property_names(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) { JSPropertyEnum *tab; uint32_t len, i; len = js_string_obj_get_length(ctx, obj); tab = NULL; if (len > 0) { /* do not allocate 0 bytes */ tab = js_malloc(ctx, sizeof(JSPropertyEnum) * len); if (!tab) return -1; for(i = 0; i < len; i++) { tab[i].atom = __JS_AtomFromUInt32(i); } } *ptab = tab; *plen = len; return 0; } 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; if (__JS_AtomIsTaggedInt(prop)) { idx = __JS_AtomToUInt32(prop); if (idx >= js_string_obj_get_length(ctx, this_obj)) goto def; if (!check_define_prop_flags(JS_PROP_ENUMERABLE, flags)) return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); /* XXX: should check if same value is configured */ 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, .get_own_property_names = js_string_get_own_property_names, .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_IsUndefined(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_IsUndefined(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_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) 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 JSValue js_string_fromCharCode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int i; StringBuffer b_s, *b = &b_s; string_buffer_init(ctx, b, argc); for(i = 0; i < argc; i++) { int32_t c; if (JS_ToInt32(ctx, &c, argv[i]) || string_buffer_putc16(b, c & 0xffff)) { string_buffer_free(b); return JS_EXCEPTION; } } return string_buffer_end(b); } static JSValue js_string_fromCodePoint(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double d; int i, c; StringBuffer b_s, *b = &b_s; /* XXX: could pre-compute string length if all arguments are JS_TAG_INT */ if (string_buffer_init(ctx, b, argc)) goto fail; for(i = 0; i < argc; i++) { if (JS_VALUE_GET_TAG(argv[i]) == JS_TAG_INT) { c = JS_VALUE_GET_INT(argv[i]); if (c < 0 || c > 0x10ffff) goto range_error; } else { if (JS_ToFloat64(ctx, &d, argv[i])) goto fail; if (d < 0 || d > 0x10ffff || (c = (int)d) != d) goto range_error; } if (string_buffer_putc(b, c)) goto fail; } return string_buffer_end(b); range_error: JS_ThrowRangeError(ctx, "invalid code point"); fail: string_buffer_free(b); return JS_EXCEPTION; } static JSValue js_string_raw(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // raw(temp,...a) JSValue cooked, val, raw; StringBuffer b_s, *b = &b_s; int64_t i, n; string_buffer_init(ctx, b, 0); raw = JS_UNDEFINED; cooked = JS_ToObject(ctx, argv[0]); if (JS_IsException(cooked)) goto exception; raw = JS_ToObjectFree(ctx, JS_GetProperty(ctx, cooked, JS_ATOM_raw)); if (JS_IsException(raw)) goto exception; if (js_get_length64(ctx, &n, raw) < 0) goto exception; for (i = 0; i < n; i++) { val = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, raw, i)); if (JS_IsException(val)) goto exception; string_buffer_concat_value_free(b, val); if (i < n - 1 && i + 1 < argc) { if (string_buffer_concat_value(b, argv[i + 1])) goto exception; } } JS_FreeValue(ctx, cooked); JS_FreeValue(ctx, raw); return string_buffer_end(b); exception: JS_FreeValue(ctx, cooked); JS_FreeValue(ctx, raw); string_buffer_free(b); return JS_EXCEPTION; } /* only used in test262 */ JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint32_t start, end, i, n; StringBuffer b_s, *b = &b_s; if (JS_ToUint32(ctx, &start, argv[0]) || JS_ToUint32(ctx, &end, argv[1])) return JS_EXCEPTION; end = min_uint32(end, 0x10ffff + 1); if (start > end) { start = end; } n = end - start; if (end > 0x10000) { n += end - max_uint32(start, 0x10000); } if (string_buffer_init2(ctx, b, n, end >= 0x100)) return JS_EXCEPTION; for(i = start; i < end; i++) { string_buffer_putc(b, i); } return string_buffer_end(b); } #if 0 static JSValue js_string___isSpace(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int c; if (JS_ToInt32(ctx, &c, argv[0])) return JS_EXCEPTION; return JS_NewBool(ctx, lre_is_space(c)); } #endif static JSValue js_string_charCodeAt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; JSString *p; int idx, c; val = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(val)) return val; p = JS_VALUE_GET_STRING(val); if (JS_ToInt32Sat(ctx, &idx, argv[0])) { JS_FreeValue(ctx, val); return JS_EXCEPTION; } if (idx < 0 || idx >= p->len) { ret = JS_NAN; } else { if (p->is_wide_char) c = p->u.str16[idx]; else c = p->u.str8[idx]; ret = JS_NewInt32(ctx, c); } JS_FreeValue(ctx, val); return ret; } static JSValue js_string_charAt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; JSString *p; int idx, c; val = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(val)) return val; p = JS_VALUE_GET_STRING(val); if (JS_ToInt32Sat(ctx, &idx, argv[0])) { JS_FreeValue(ctx, val); return JS_EXCEPTION; } if (idx < 0 || idx >= p->len) { ret = js_new_string8(ctx, NULL, 0); } else { if (p->is_wide_char) c = p->u.str16[idx]; else c = p->u.str8[idx]; ret = js_new_string_char(ctx, c); } JS_FreeValue(ctx, val); return ret; } static JSValue js_string_codePointAt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; JSString *p; int idx, c; val = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(val)) return val; p = JS_VALUE_GET_STRING(val); if (JS_ToInt32Sat(ctx, &idx, argv[0])) { JS_FreeValue(ctx, val); return JS_EXCEPTION; } if (idx < 0 || idx >= p->len) { ret = JS_UNDEFINED; } else { c = string_getc(p, &idx); ret = JS_NewInt32(ctx, c); } JS_FreeValue(ctx, val); return ret; } 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 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 int string_advance_index(JSString *p, int index, BOOL unicode) { if (!unicode || (unsigned)index >= p->len || !p->is_wide_char) { index++; } else { string_getc(p, &index); } return index; } static JSValue js_string_indexOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int lastIndexOf) { JSValue str, v; int i, len, v_len, pos, start, stop, ret, inc; JSString *p; JSString *p1; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) return str; v = JS_ToString(ctx, argv[0]); if (JS_IsException(v)) goto fail; p = JS_VALUE_GET_STRING(str); p1 = JS_VALUE_GET_STRING(v); len = p->len; v_len = p1->len; if (lastIndexOf) { pos = len - v_len; if (argc > 1) { double d; if (JS_ToFloat64(ctx, &d, argv[1])) goto fail; if (!isnan(d)) { if (d <= 0) pos = 0; else if (d < pos) pos = d; } } start = pos; stop = 0; inc = -1; } else { pos = 0; if (argc > 1) { if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) goto fail; } start = pos; stop = len - v_len; inc = 1; } ret = -1; if (len >= v_len && inc * (stop - start) >= 0) { for (i = start;; i += inc) { if (!string_cmp(p, p1, i, 0, v_len)) { ret = i; break; } if (i == stop) break; } } JS_FreeValue(ctx, str); JS_FreeValue(ctx, v); return JS_NewInt32(ctx, ret); fail: JS_FreeValue(ctx, str); JS_FreeValue(ctx, v); return JS_EXCEPTION; } /* return < 0 if exception or TRUE/FALSE */ static int js_is_regexp(JSContext *ctx, JSValueConst obj); static JSValue js_string_includes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue str, v = JS_UNDEFINED; int i, len, v_len, pos, start, stop, ret; JSString *p; JSString *p1; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) return str; ret = js_is_regexp(ctx, argv[0]); if (ret) { if (ret > 0) JS_ThrowTypeError(ctx, "regex not supported"); goto fail; } v = JS_ToString(ctx, argv[0]); if (JS_IsException(v)) goto fail; p = JS_VALUE_GET_STRING(str); p1 = JS_VALUE_GET_STRING(v); len = p->len; v_len = p1->len; pos = (magic & 2) ? len : 0; if (argc > 1 && !JS_IsUndefined(argv[1])) { if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) goto fail; } len -= v_len; start = pos; stop = len; if (magic & 1) { stop = pos; } if (magic & 2) { pos -= v_len; start = stop = pos; } ret = 0; if (start >= 0 && start <= stop) { for (i = start;; i++) { if (!string_cmp(p, p1, i, 0, v_len)) { ret = 1; break; } if (i == stop) break; } } JS_FreeValue(ctx, str); JS_FreeValue(ctx, v); return JS_NewBool(ctx, ret); fail: JS_FreeValue(ctx, str); JS_FreeValue(ctx, v); return JS_EXCEPTION; } static int check_regexp_g_flag(JSContext *ctx, JSValueConst regexp) { int ret; JSValue flags; ret = js_is_regexp(ctx, regexp); if (ret < 0) return -1; if (ret) { flags = JS_GetProperty(ctx, regexp, JS_ATOM_flags); if (JS_IsException(flags)) return -1; if (JS_IsUndefined(flags) || JS_IsNull(flags)) { JS_ThrowTypeError(ctx, "cannot convert to object"); return -1; } flags = JS_ToStringFree(ctx, flags); if (JS_IsException(flags)) return -1; ret = string_indexof_char(JS_VALUE_GET_STRING(flags), 'g', 0); JS_FreeValue(ctx, flags); if (ret < 0) { JS_ThrowTypeError(ctx, "regexp must have the 'g' flag"); return -1; } } return 0; } static JSValue js_string_match(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int atom) { // match(rx), search(rx), matchAll(rx) // atom is JS_ATOM_Symbol_match, JS_ATOM_Symbol_search, or JS_ATOM_Symbol_matchAll JSValueConst O = this_val, regexp = argv[0], args[2]; JSValue matcher, S, rx, result, str; int args_len; if (JS_IsUndefined(O) || JS_IsNull(O)) return JS_ThrowTypeError(ctx, "cannot convert to object"); if (!JS_IsUndefined(regexp) && !JS_IsNull(regexp)) { matcher = JS_GetProperty(ctx, regexp, atom); if (JS_IsException(matcher)) return JS_EXCEPTION; if (atom == JS_ATOM_Symbol_matchAll) { if (check_regexp_g_flag(ctx, regexp) < 0) { JS_FreeValue(ctx, matcher); return JS_EXCEPTION; } } if (!JS_IsUndefined(matcher) && !JS_IsNull(matcher)) { return JS_CallFree(ctx, matcher, regexp, 1, &O); } } S = JS_ToString(ctx, O); if (JS_IsException(S)) return JS_EXCEPTION; args_len = 1; args[0] = regexp; str = JS_UNDEFINED; if (atom == JS_ATOM_Symbol_matchAll) { str = JS_NewString(ctx, "g"); if (JS_IsException(str)) goto fail; args[args_len++] = (JSValueConst)str; } rx = JS_CallConstructor(ctx, ctx->regexp_ctor, args_len, args); JS_FreeValue(ctx, str); if (JS_IsException(rx)) { fail: JS_FreeValue(ctx, S); return JS_EXCEPTION; } result = JS_InvokeFree(ctx, rx, atom, 1, (JSValueConst *)&S); JS_FreeValue(ctx, S); return result; } 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_IsUndefined(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'; 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_IsUndefined(s)) { if (string_buffer_concat_value_free(b, s)) goto exception; } } else { goto norep; } } else if (c == '<' && !JS_IsUndefined(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_IsUndefined(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; } static JSValue js_string_replace(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int is_replaceAll) { // replace(rx, rep) JSValueConst O = this_val, searchValue = argv[0], replaceValue = argv[1]; JSValueConst args[6]; JSValue str, search_str, replaceValue_str, repl_str; JSString *sp, *searchp; StringBuffer b_s, *b = &b_s; int pos, functionalReplace, endOfLastMatch; BOOL is_first; if (JS_IsUndefined(O) || JS_IsNull(O)) return JS_ThrowTypeError(ctx, "cannot convert to object"); search_str = JS_UNDEFINED; replaceValue_str = JS_UNDEFINED; repl_str = JS_UNDEFINED; if (!JS_IsUndefined(searchValue) && !JS_IsNull(searchValue)) { JSValue replacer; if (is_replaceAll) { if (check_regexp_g_flag(ctx, searchValue) < 0) return JS_EXCEPTION; } replacer = JS_GetProperty(ctx, searchValue, JS_ATOM_Symbol_replace); if (JS_IsException(replacer)) return JS_EXCEPTION; if (!JS_IsUndefined(replacer)) { args[0] = O; args[1] = replaceValue; return JS_CallFree(ctx, replacer, searchValue, 2, args); } } string_buffer_init(ctx, b, 0); str = JS_ToString(ctx, O); if (JS_IsException(str)) goto exception; search_str = JS_ToString(ctx, searchValue); if (JS_IsException(search_str)) goto exception; functionalReplace = JS_IsFunction(ctx, replaceValue); if (!functionalReplace) { replaceValue_str = JS_ToString(ctx, replaceValue); if (JS_IsException(replaceValue_str)) goto exception; } sp = JS_VALUE_GET_STRING(str); searchp = JS_VALUE_GET_STRING(search_str); endOfLastMatch = 0; is_first = TRUE; for(;;) { if (unlikely(searchp->len == 0)) { if (is_first) pos = 0; else if (endOfLastMatch >= sp->len) pos = -1; else pos = endOfLastMatch + 1; } else { pos = string_indexof(sp, searchp, endOfLastMatch); } if (pos < 0) { if (is_first) { string_buffer_free(b); JS_FreeValue(ctx, search_str); JS_FreeValue(ctx, replaceValue_str); return str; } else { break; } } if (functionalReplace) { args[0] = search_str; args[1] = JS_NewInt32(ctx, pos); args[2] = str; repl_str = JS_ToStringFree(ctx, JS_Call(ctx, replaceValue, JS_UNDEFINED, 3, args)); } else { args[0] = search_str; args[1] = str; args[2] = JS_NewInt32(ctx, pos); args[3] = JS_UNDEFINED; args[4] = JS_UNDEFINED; args[5] = replaceValue_str; repl_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args); } if (JS_IsException(repl_str)) goto exception; string_buffer_concat(b, sp, endOfLastMatch, pos); string_buffer_concat_value_free(b, repl_str); endOfLastMatch = pos + searchp->len; is_first = FALSE; if (!is_replaceAll) break; } string_buffer_concat(b, sp, endOfLastMatch, sp->len); JS_FreeValue(ctx, search_str); JS_FreeValue(ctx, replaceValue_str); JS_FreeValue(ctx, str); return string_buffer_end(b); exception: string_buffer_free(b); JS_FreeValue(ctx, search_str); JS_FreeValue(ctx, replaceValue_str); JS_FreeValue(ctx, str); return JS_EXCEPTION; } static JSValue js_string_split(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // split(sep, limit) JSValueConst O = this_val, separator = argv[0], limit = argv[1]; JSValueConst args[2]; JSValue S, A, R, T; uint32_t lim, lengthA; int64_t p, q, s, r, e; JSString *sp, *rp; if (JS_IsUndefined(O) || JS_IsNull(O)) return JS_ThrowTypeError(ctx, "cannot convert to object"); S = JS_UNDEFINED; A = JS_UNDEFINED; R = JS_UNDEFINED; if (!JS_IsUndefined(separator) && !JS_IsNull(separator)) { JSValue splitter; splitter = JS_GetProperty(ctx, separator, JS_ATOM_Symbol_split); if (JS_IsException(splitter)) return JS_EXCEPTION; if (!JS_IsUndefined(splitter) && !JS_IsNull(splitter)) { args[0] = O; args[1] = limit; return JS_CallFree(ctx, splitter, separator, 2, args); } } S = JS_ToString(ctx, O); if (JS_IsException(S)) goto exception; A = JS_NewArray(ctx); if (JS_IsException(A)) goto exception; lengthA = 0; if (JS_IsUndefined(limit)) { lim = 0xffffffff; } else { if (JS_ToUint32(ctx, &lim, limit) < 0) goto exception; } sp = JS_VALUE_GET_STRING(S); s = sp->len; R = JS_ToString(ctx, separator); if (JS_IsException(R)) goto exception; rp = JS_VALUE_GET_STRING(R); r = rp->len; p = 0; if (lim == 0) goto done; if (JS_IsUndefined(separator)) goto add_tail; if (s == 0) { if (r != 0) goto add_tail; goto done; } q = p; for (q = p; (q += !r) <= s - r - !r; q = p = e + r) { e = string_indexof(sp, rp, q); if (e < 0) break; T = js_sub_string(ctx, sp, p, e); if (JS_IsException(T)) goto exception; if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T, 0) < 0) goto exception; if (lengthA == lim) goto done; } add_tail: T = js_sub_string(ctx, sp, p, s); if (JS_IsException(T)) goto exception; if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T,0 ) < 0) goto exception; done: JS_FreeValue(ctx, S); JS_FreeValue(ctx, R); return A; exception: JS_FreeValue(ctx, A); JS_FreeValue(ctx, S); JS_FreeValue(ctx, R); return JS_EXCEPTION; } static JSValue js_string_substring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str, ret; int a, b, start, end; JSString *p; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) return str; p = JS_VALUE_GET_STRING(str); if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, p->len, 0)) { JS_FreeValue(ctx, str); return JS_EXCEPTION; } b = p->len; if (!JS_IsUndefined(argv[1])) { if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, p->len, 0)) { JS_FreeValue(ctx, str); return JS_EXCEPTION; } } if (a < b) { start = a; end = b; } else { start = b; end = a; } ret = js_sub_string(ctx, p, start, end); JS_FreeValue(ctx, str); return ret; } static JSValue js_string_substr(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str, ret; int a, len, n; JSString *p; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) return str; p = JS_VALUE_GET_STRING(str); len = p->len; if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, len)) { JS_FreeValue(ctx, str); return JS_EXCEPTION; } n = len - a; if (!JS_IsUndefined(argv[1])) { if (JS_ToInt32Clamp(ctx, &n, argv[1], 0, len - a, 0)) { JS_FreeValue(ctx, str); return JS_EXCEPTION; } } ret = js_sub_string(ctx, p, a, a + n); JS_FreeValue(ctx, str); return ret; } static JSValue js_string_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str, ret; int len, start, end; JSString *p; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) return str; p = JS_VALUE_GET_STRING(str); len = p->len; if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) { JS_FreeValue(ctx, str); return JS_EXCEPTION; } end = len; if (!JS_IsUndefined(argv[1])) { if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) { JS_FreeValue(ctx, str); return JS_EXCEPTION; } } ret = js_sub_string(ctx, p, start, max_int(end, start)); JS_FreeValue(ctx, str); return ret; } static JSValue js_string_pad(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int padEnd) { JSValue str, v = JS_UNDEFINED; StringBuffer b_s, *b = &b_s; JSString *p, *p1 = NULL; int n, len, c = ' '; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) goto fail1; if (JS_ToInt32Sat(ctx, &n, argv[0])) goto fail2; p = JS_VALUE_GET_STRING(str); len = p->len; if (len >= n) return str; if (n > JS_STRING_LEN_MAX) { JS_ThrowInternalError(ctx, "string too long"); goto fail2; } if (argc > 1 && !JS_IsUndefined(argv[1])) { v = JS_ToString(ctx, argv[1]); if (JS_IsException(v)) goto fail2; p1 = JS_VALUE_GET_STRING(v); if (p1->len == 0) { JS_FreeValue(ctx, v); return str; } if (p1->len == 1) { c = string_get(p1, 0); p1 = NULL; } } if (string_buffer_init(ctx, b, n)) goto fail3; n -= len; if (padEnd) { if (string_buffer_concat(b, p, 0, len)) goto fail; } if (p1) { while (n > 0) { int chunk = min_int(n, p1->len); if (string_buffer_concat(b, p1, 0, chunk)) goto fail; n -= chunk; } } else { if (string_buffer_fill(b, c, n)) goto fail; } if (!padEnd) { if (string_buffer_concat(b, p, 0, len)) goto fail; } JS_FreeValue(ctx, v); JS_FreeValue(ctx, str); return string_buffer_end(b); fail: string_buffer_free(b); fail3: JS_FreeValue(ctx, v); fail2: JS_FreeValue(ctx, str); fail1: return JS_EXCEPTION; } static JSValue js_string_repeat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str; StringBuffer b_s, *b = &b_s; JSString *p; int64_t val; int n, len; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) goto fail; if (JS_ToInt64Sat(ctx, &val, argv[0])) goto fail; if (val < 0 || val > 2147483647) { JS_ThrowRangeError(ctx, "invalid repeat count"); goto fail; } n = val; p = JS_VALUE_GET_STRING(str); len = p->len; if (len == 0 || n == 1) return str; if (val * len > JS_STRING_LEN_MAX) { JS_ThrowInternalError(ctx, "string too long"); goto fail; } if (string_buffer_init2(ctx, b, n * len, p->is_wide_char)) goto fail; if (len == 1) { string_buffer_fill(b, string_get(p, 0), n); } else { while (n-- > 0) { string_buffer_concat(b, p, 0, len); } } JS_FreeValue(ctx, str); return string_buffer_end(b); fail: JS_FreeValue(ctx, str); return JS_EXCEPTION; } static JSValue js_string_trim(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue str, ret; int a, b, len; JSString *p; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) return str; p = JS_VALUE_GET_STRING(str); a = 0; b = len = p->len; if (magic & 1) { while (a < len && lre_is_space(string_get(p, a))) a++; } if (magic & 2) { while (b > a && lre_is_space(string_get(p, b - 1))) b--; } ret = js_sub_string(ctx, p, a, b); JS_FreeValue(ctx, str); return ret; } static JSValue js_string___quote(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ToQuotedString(ctx, this_val); } /* return 0 if before the first char */ static int string_prevc(JSString *p, int *pidx) { int idx, c, c1; idx = *pidx; if (idx <= 0) return 0; idx--; if (p->is_wide_char) { c = p->u.str16[idx]; if (c >= 0xdc00 && c < 0xe000 && idx > 0) { c1 = p->u.str16[idx - 1]; if (c1 >= 0xd800 && c1 <= 0xdc00) { c = (((c1 & 0x3ff) << 10) | (c & 0x3ff)) + 0x10000; idx--; } } } else { c = p->u.str8[idx]; } *pidx = idx; return c; } static BOOL test_final_sigma(JSString *p, int sigma_pos) { int k, c1; /* before C: skip case ignorable chars and check there is a cased letter */ k = sigma_pos; for(;;) { c1 = string_prevc(p, &k); if (!lre_is_case_ignorable(c1)) break; } if (!lre_is_cased(c1)) return FALSE; /* after C: skip case ignorable chars and check there is no cased letter */ k = sigma_pos + 1; for(;;) { if (k >= p->len) return TRUE; c1 = string_getc(p, &k); if (!lre_is_case_ignorable(c1)) break; } return !lre_is_cased(c1); } static JSValue js_string_localeCompare(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue a, b; int cmp; a = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(a)) return JS_EXCEPTION; b = JS_ToString(ctx, argv[0]); if (JS_IsException(b)) { JS_FreeValue(ctx, a); return JS_EXCEPTION; } cmp = js_string_compare(ctx, JS_VALUE_GET_STRING(a), JS_VALUE_GET_STRING(b)); JS_FreeValue(ctx, a); JS_FreeValue(ctx, b); return JS_NewInt32(ctx, cmp); } static JSValue js_string_toLowerCase(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int to_lower) { JSValue val; StringBuffer b_s, *b = &b_s; JSString *p; int i, c, j, l; uint32_t res[LRE_CC_RES_LEN_MAX]; val = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(val)) return val; p = JS_VALUE_GET_STRING(val); if (p->len == 0) return val; if (string_buffer_init(ctx, b, p->len)) goto fail; for(i = 0; i < p->len;) { c = string_getc(p, &i); if (c == 0x3a3 && to_lower && test_final_sigma(p, i - 1)) { res[0] = 0x3c2; /* final sigma */ l = 1; } else { l = lre_case_conv(res, c, to_lower); } for(j = 0; j < l; j++) { if (string_buffer_putc(b, res[j])) goto fail; } } JS_FreeValue(ctx, val); return string_buffer_end(b); fail: JS_FreeValue(ctx, val); string_buffer_free(b); return JS_EXCEPTION; } #ifdef CONFIG_ALL_UNICODE /* return (-1, NULL) if exception, otherwise (len, buf) */ static int JS_ToUTF32String(JSContext *ctx, uint32_t **pbuf, JSValueConst val1) { JSValue val; JSString *p; uint32_t *buf; int i, j, len; val = JS_ToString(ctx, val1); if (JS_IsException(val)) return -1; p = JS_VALUE_GET_STRING(val); len = p->len; /* UTF32 buffer length is len minus the number of correct surrogates pairs */ buf = js_malloc(ctx, sizeof(buf[0]) * max_int(len, 1)); if (!buf) { JS_FreeValue(ctx, val); goto fail; } for(i = j = 0; i < len;) buf[j++] = string_getc(p, &i); JS_FreeValue(ctx, val); *pbuf = buf; return j; fail: *pbuf = NULL; return -1; } static JSValue JS_NewUTF32String(JSContext *ctx, const uint32_t *buf, int len) { int i; StringBuffer b_s, *b = &b_s; if (string_buffer_init(ctx, b, len)) return JS_EXCEPTION; for(i = 0; i < len; i++) { if (string_buffer_putc(b, buf[i])) goto fail; } return string_buffer_end(b); fail: string_buffer_free(b); return JS_EXCEPTION; } static JSValue js_string_normalize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *form, *p; size_t form_len; int is_compat, buf_len, out_len; UnicodeNormalizationEnum n_type; JSValue val; uint32_t *buf, *out_buf; val = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(val)) return val; buf_len = JS_ToUTF32String(ctx, &buf, val); JS_FreeValue(ctx, val); if (buf_len < 0) return JS_EXCEPTION; if (argc == 0 || JS_IsUndefined(argv[0])) { n_type = UNICODE_NFC; } else { form = JS_ToCStringLen(ctx, &form_len, argv[0]); if (!form) goto fail1; p = form; if (p[0] != 'N' || p[1] != 'F') goto bad_form; p += 2; is_compat = FALSE; if (*p == 'K') { is_compat = TRUE; p++; } if (*p == 'C' || *p == 'D') { n_type = UNICODE_NFC + is_compat * 2 + (*p - 'C'); if ((p + 1 - form) != form_len) goto bad_form; } else { bad_form: JS_FreeCString(ctx, form); JS_ThrowRangeError(ctx, "bad normalization form"); fail1: js_free(ctx, buf); return JS_EXCEPTION; } JS_FreeCString(ctx, form); } out_len = unicode_normalize(&out_buf, buf, buf_len, n_type, ctx->rt, (DynBufReallocFunc *)js_realloc_rt); js_free(ctx, buf); if (out_len < 0) return JS_EXCEPTION; val = JS_NewUTF32String(ctx, out_buf, out_len); js_free(ctx, out_buf); return val; } #endif /* CONFIG_ALL_UNICODE */ /* 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); } #if 0 static JSValue js_string___toStringCheckObject(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ToStringCheckObject(ctx, argv[0]); } static JSValue js_string___toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ToString(ctx, argv[0]); } static JSValue js_string___advanceStringIndex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str; int idx; BOOL is_unicode; JSString *p; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; if (JS_ToInt32Sat(ctx, &idx, argv[1])) { JS_FreeValue(ctx, str); return JS_EXCEPTION; } is_unicode = JS_ToBool(ctx, argv[2]); p = JS_VALUE_GET_STRING(str); if (!is_unicode || (unsigned)idx >= p->len || !p->is_wide_char) { idx++; } else { string_getc(p, &idx); } JS_FreeValue(ctx, str); return JS_NewInt32(ctx, idx); } #endif /* String Iterator */ static JSValue js_string_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, BOOL *pdone, int magic) { JSArrayIteratorData *it; uint32_t idx, c, start; JSString *p; it = JS_GetOpaque2(ctx, this_val, JS_CLASS_STRING_ITERATOR); if (!it) { *pdone = FALSE; return JS_EXCEPTION; } if (JS_IsUndefined(it->obj)) goto done; p = JS_VALUE_GET_STRING(it->obj); idx = it->idx; if (idx >= p->len) { JS_FreeValue(ctx, it->obj); it->obj = JS_UNDEFINED; done: *pdone = TRUE; return JS_UNDEFINED; } start = idx; c = string_getc(p, (int *)&idx); it->idx = idx; *pdone = FALSE; if (c <= 0xffff) { return js_new_string_char(ctx, c); } else { return js_new_string16(ctx, p->u.str16 + start, 2); } } /* ES6 Annex B 2.3.2 etc. */ enum { magic_string_anchor, magic_string_big, magic_string_blink, magic_string_bold, magic_string_fixed, magic_string_fontcolor, magic_string_fontsize, magic_string_italics, magic_string_link, magic_string_small, magic_string_strike, magic_string_sub, magic_string_sup, }; static JSValue js_string_CreateHTML(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue str; const JSString *p; StringBuffer b_s, *b = &b_s; static struct { const char *tag, *attr; } const defs[] = { { "a", "name" }, { "big", NULL }, { "blink", NULL }, { "b", NULL }, { "tt", NULL }, { "font", "color" }, { "font", "size" }, { "i", NULL }, { "a", "href" }, { "small", NULL }, { "strike", NULL }, { "sub", NULL }, { "sup", NULL }, }; str = JS_ToStringCheckObject(ctx, this_val); if (JS_IsException(str)) return JS_EXCEPTION; string_buffer_init(ctx, b, 7); string_buffer_putc8(b, '<'); string_buffer_puts8(b, defs[magic].tag); if (defs[magic].attr) { // r += " " + attr + "=\"" + value + "\""; JSValue value; int i; string_buffer_putc8(b, ' '); string_buffer_puts8(b, defs[magic].attr); string_buffer_puts8(b, "=\""); value = JS_ToStringCheckObject(ctx, argv[0]); if (JS_IsException(value)) { JS_FreeValue(ctx, str); string_buffer_free(b); return JS_EXCEPTION; } p = JS_VALUE_GET_STRING(value); for (i = 0; i < p->len; i++) { int c = string_get(p, i); if (c == '"') { string_buffer_puts8(b, """); } else { string_buffer_putc16(b, c); } } JS_FreeValue(ctx, value); string_buffer_putc8(b, '\"'); } // return r + ">" + str + ""; string_buffer_putc8(b, '>'); string_buffer_concat_value_free(b, str); string_buffer_puts8(b, "'); return string_buffer_end(b); } static const JSCFunctionListEntry js_string_funcs[] = { JS_CFUNC_DEF("fromCharCode", 1, js_string_fromCharCode ), JS_CFUNC_DEF("fromCodePoint", 1, js_string_fromCodePoint ), JS_CFUNC_DEF("raw", 1, js_string_raw ), //JS_CFUNC_DEF("__toString", 1, js_string___toString ), //JS_CFUNC_DEF("__isSpace", 1, js_string___isSpace ), //JS_CFUNC_DEF("__toStringCheckObject", 1, js_string___toStringCheckObject ), //JS_CFUNC_DEF("__advanceStringIndex", 3, js_string___advanceStringIndex ), //JS_CFUNC_DEF("__GetSubstitution", 6, js_string___GetSubstitution ), }; static const JSCFunctionListEntry js_string_proto_funcs[] = { JS_PROP_INT32_DEF("length", 0, JS_PROP_CONFIGURABLE ), JS_CFUNC_DEF("charCodeAt", 1, js_string_charCodeAt ), JS_CFUNC_DEF("charAt", 1, js_string_charAt ), JS_CFUNC_DEF("concat", 1, js_string_concat ), JS_CFUNC_DEF("codePointAt", 1, js_string_codePointAt ), JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ), JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ), JS_CFUNC_MAGIC_DEF("includes", 1, js_string_includes, 0 ), JS_CFUNC_MAGIC_DEF("endsWith", 1, js_string_includes, 2 ), JS_CFUNC_MAGIC_DEF("startsWith", 1, js_string_includes, 1 ), JS_CFUNC_MAGIC_DEF("match", 1, js_string_match, JS_ATOM_Symbol_match ), JS_CFUNC_MAGIC_DEF("matchAll", 1, js_string_match, JS_ATOM_Symbol_matchAll ), JS_CFUNC_MAGIC_DEF("search", 1, js_string_match, JS_ATOM_Symbol_search ), JS_CFUNC_DEF("split", 2, js_string_split ), JS_CFUNC_DEF("substring", 2, js_string_substring ), JS_CFUNC_DEF("substr", 2, js_string_substr ), JS_CFUNC_DEF("slice", 2, js_string_slice ), JS_CFUNC_DEF("repeat", 1, js_string_repeat ), JS_CFUNC_MAGIC_DEF("replace", 2, js_string_replace, 0 ), JS_CFUNC_MAGIC_DEF("replaceAll", 2, js_string_replace, 1 ), JS_CFUNC_MAGIC_DEF("padEnd", 1, js_string_pad, 1 ), JS_CFUNC_MAGIC_DEF("padStart", 1, js_string_pad, 0 ), JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ), JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ), JS_ALIAS_DEF("trimRight", "trimEnd" ), JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ), JS_ALIAS_DEF("trimLeft", "trimStart" ), JS_CFUNC_DEF("toString", 0, js_string_toString ), JS_CFUNC_DEF("valueOf", 0, js_string_toString ), JS_CFUNC_DEF("__quote", 1, js_string___quote ), JS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare ), JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), JS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1 ), JS_CFUNC_MAGIC_DEF("toLocaleUpperCase", 0, js_string_toLowerCase, 0 ), JS_CFUNC_MAGIC_DEF("[Symbol.iterator]", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE | 4 ), /* ES6 Annex B 2.3.2 etc. */ JS_CFUNC_MAGIC_DEF("anchor", 1, js_string_CreateHTML, magic_string_anchor ), JS_CFUNC_MAGIC_DEF("big", 0, js_string_CreateHTML, magic_string_big ), JS_CFUNC_MAGIC_DEF("blink", 0, js_string_CreateHTML, magic_string_blink ), JS_CFUNC_MAGIC_DEF("bold", 0, js_string_CreateHTML, magic_string_bold ), JS_CFUNC_MAGIC_DEF("fixed", 0, js_string_CreateHTML, magic_string_fixed ), JS_CFUNC_MAGIC_DEF("fontcolor", 1, js_string_CreateHTML, magic_string_fontcolor ), JS_CFUNC_MAGIC_DEF("fontsize", 1, js_string_CreateHTML, magic_string_fontsize ), JS_CFUNC_MAGIC_DEF("italics", 0, js_string_CreateHTML, magic_string_italics ), JS_CFUNC_MAGIC_DEF("link", 1, js_string_CreateHTML, magic_string_link ), JS_CFUNC_MAGIC_DEF("small", 0, js_string_CreateHTML, magic_string_small ), JS_CFUNC_MAGIC_DEF("strike", 0, js_string_CreateHTML, magic_string_strike ), JS_CFUNC_MAGIC_DEF("sub", 0, js_string_CreateHTML, magic_string_sub ), JS_CFUNC_MAGIC_DEF("sup", 0, js_string_CreateHTML, magic_string_sup ), }; static const JSCFunctionListEntry js_string_iterator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 0, js_string_iterator_next, 0 ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "String Iterator", JS_PROP_CONFIGURABLE ), }; #ifdef CONFIG_ALL_UNICODE static const JSCFunctionListEntry js_string_proto_normalize[] = { JS_CFUNC_DEF("normalize", 0, js_string_normalize ), }; #endif void JS_AddIntrinsicStringNormalize(JSContext *ctx) { #ifdef CONFIG_ALL_UNICODE JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_normalize, countof(js_string_proto_normalize)); #endif } /* Math */ /* precondition: a and b are not NaN */ static double js_fmin(double a, double b) { if (a == 0 && b == 0) { JSFloat64Union a1, b1; a1.d = a; b1.d = b; a1.u64 |= b1.u64; return a1.d; } else { return fmin(a, b); } } /* precondition: a and b are not NaN */ static double js_fmax(double a, double b) { if (a == 0 && b == 0) { JSFloat64Union a1, b1; a1.d = a; b1.d = b; a1.u64 &= b1.u64; return a1.d; } else { return fmax(a, b); } } static JSValue js_math_min_max(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { BOOL is_max = magic; double r, a; int i; uint32_t tag; if (unlikely(argc == 0)) { return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0); } tag = JS_VALUE_GET_TAG(argv[0]); if (tag == JS_TAG_INT) { int a1, r1 = JS_VALUE_GET_INT(argv[0]); for(i = 1; i < argc; i++) { tag = JS_VALUE_GET_TAG(argv[i]); if (tag != JS_TAG_INT) { r = r1; goto generic_case; } a1 = JS_VALUE_GET_INT(argv[i]); if (is_max) r1 = max_int(r1, a1); else r1 = min_int(r1, a1); } return JS_NewInt32(ctx, r1); } else { if (JS_ToFloat64(ctx, &r, argv[0])) return JS_EXCEPTION; i = 1; generic_case: while (i < argc) { if (JS_ToFloat64(ctx, &a, argv[i])) return JS_EXCEPTION; if (!isnan(r)) { if (isnan(a)) { r = a; } else { if (is_max) r = js_fmax(r, a); else r = js_fmin(r, a); } } i++; } return JS_NewFloat64(ctx, r); } } static double js_math_sign(double a) { if (isnan(a) || a == 0.0) return a; if (a < 0) return -1; else return 1; } static double js_math_round(double a) { JSFloat64Union u; uint64_t frac_mask, one; unsigned int e, s; u.d = a; e = (u.u64 >> 52) & 0x7ff; if (e < 1023) { /* abs(a) < 1 */ if (e == (1023 - 1) && u.u64 != 0xbfe0000000000000) { /* abs(a) > 0.5 or a = 0.5: return +/-1.0 */ u.u64 = (u.u64 & ((uint64_t)1 << 63)) | ((uint64_t)1023 << 52); } else { /* return +/-0.0 */ u.u64 &= (uint64_t)1 << 63; } } else if (e < (1023 + 52)) { s = u.u64 >> 63; one = (uint64_t)1 << (52 - (e - 1023)); frac_mask = one - 1; u.u64 += (one >> 1) - s; u.u64 &= ~frac_mask; /* truncate to an integer */ } /* otherwise: abs(a) >= 2^52, or NaN, +/-Infinity: no change */ return u.d; } static JSValue js_math_hypot(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double r, a, b; int i; if (argc == 2) { /* use the more precise built-in function when possible */ if (JS_ToFloat64(ctx, &a, argv[0])) return JS_EXCEPTION; if (JS_ToFloat64(ctx, &b, argv[1])) return JS_EXCEPTION; r = hypot(a, b); } else { r = 0; for(i = 0; i < argc; i++) { if (JS_ToFloat64(ctx, &a, argv[i])) return JS_EXCEPTION; r += a; } r = sqrt(r); } return JS_NewFloat64(ctx, r); } static double js_math_fround(double a) { return (float)a; } static JSValue js_math_imul(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int a, b; if (JS_ToInt32(ctx, &a, argv[0])) return JS_EXCEPTION; if (JS_ToInt32(ctx, &b, argv[1])) return JS_EXCEPTION; /* purposely ignoring overflow */ return JS_NewInt32(ctx, a * b); } static JSValue js_math_clz32(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint32_t a, r; if (JS_ToUint32(ctx, &a, argv[0])) return JS_EXCEPTION; if (a == 0) r = 32; else r = clz32(a); return JS_NewInt32(ctx, r); } /* xorshift* random number generator by Marsaglia */ static uint64_t xorshift64star(uint64_t *pstate) { uint64_t x; x = *pstate; x ^= x >> 12; x ^= x << 25; x ^= x >> 27; *pstate = x; return x * 0x2545F4914F6CDD1D; } static void js_random_init(JSContext *ctx) { struct timeval tv; gettimeofday(&tv, NULL); ctx->random_state = ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec; /* the state must be non zero */ if (ctx->random_state == 0) ctx->random_state = 1; } static JSValue js_math_random(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSFloat64Union u; uint64_t v; v = xorshift64star(&ctx->random_state); /* 1.0 <= u.d < 2 */ u.u64 = ((uint64_t)0x3ff << 52) | (v >> 12); return __JS_NewFloat64(ctx, u.d - 1.0); } static const JSCFunctionListEntry js_math_funcs[] = { JS_CFUNC_MAGIC_DEF("min", 2, js_math_min_max, 0 ), JS_CFUNC_MAGIC_DEF("max", 2, js_math_min_max, 1 ), JS_CFUNC_SPECIAL_DEF("abs", 1, f_f, fabs ), JS_CFUNC_SPECIAL_DEF("floor", 1, f_f, floor ), JS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, ceil ), JS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_math_round ), JS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, sqrt ), JS_CFUNC_SPECIAL_DEF("acos", 1, f_f, acos ), JS_CFUNC_SPECIAL_DEF("asin", 1, f_f, asin ), JS_CFUNC_SPECIAL_DEF("atan", 1, f_f, atan ), JS_CFUNC_SPECIAL_DEF("atan2", 2, f_f_f, atan2 ), JS_CFUNC_SPECIAL_DEF("cos", 1, f_f, cos ), JS_CFUNC_SPECIAL_DEF("exp", 1, f_f, exp ), JS_CFUNC_SPECIAL_DEF("log", 1, f_f, log ), JS_CFUNC_SPECIAL_DEF("pow", 2, f_f_f, js_pow ), JS_CFUNC_SPECIAL_DEF("sin", 1, f_f, sin ), JS_CFUNC_SPECIAL_DEF("tan", 1, f_f, tan ), /* ES6 */ JS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, trunc ), JS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign ), JS_CFUNC_SPECIAL_DEF("cosh", 1, f_f, cosh ), JS_CFUNC_SPECIAL_DEF("sinh", 1, f_f, sinh ), JS_CFUNC_SPECIAL_DEF("tanh", 1, f_f, tanh ), JS_CFUNC_SPECIAL_DEF("acosh", 1, f_f, acosh ), JS_CFUNC_SPECIAL_DEF("asinh", 1, f_f, asinh ), JS_CFUNC_SPECIAL_DEF("atanh", 1, f_f, atanh ), JS_CFUNC_SPECIAL_DEF("expm1", 1, f_f, expm1 ), JS_CFUNC_SPECIAL_DEF("log1p", 1, f_f, log1p ), JS_CFUNC_SPECIAL_DEF("log2", 1, f_f, log2 ), JS_CFUNC_SPECIAL_DEF("log10", 1, f_f, log10 ), JS_CFUNC_SPECIAL_DEF("cbrt", 1, f_f, cbrt ), JS_CFUNC_DEF("hypot", 2, js_math_hypot ), JS_CFUNC_DEF("random", 0, js_math_random ), JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ), JS_CFUNC_DEF("imul", 2, js_math_imul ), JS_CFUNC_DEF("clz32", 1, js_math_clz32 ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Math", JS_PROP_CONFIGURABLE ), JS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0 ), JS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0 ), JS_PROP_DOUBLE_DEF("LN2", 0.6931471805599453, 0 ), JS_PROP_DOUBLE_DEF("LOG2E", 1.4426950408889634, 0 ), JS_PROP_DOUBLE_DEF("LOG10E", 0.4342944819032518, 0 ), JS_PROP_DOUBLE_DEF("PI", 3.141592653589793, 0 ), JS_PROP_DOUBLE_DEF("SQRT1_2", 0.7071067811865476, 0 ), JS_PROP_DOUBLE_DEF("SQRT2", 1.4142135623730951, 0 ), }; static const JSCFunctionListEntry js_math_obj[] = { JS_OBJECT_DEF("Math", js_math_funcs, countof(js_math_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; /* Date */ #if 0 /* OS dependent: return the UTC time in ms since 1970. */ static JSValue js___date_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int64_t d; struct timeval tv; gettimeofday(&tv, NULL); d = (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); return JS_NewInt64(ctx, d); } #endif /* OS dependent: return the UTC time in microseconds since 1970. */ static JSValue js___date_clock(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int64_t d; struct timeval tv; gettimeofday(&tv, NULL); d = (int64_t)tv.tv_sec * 1000000 + tv.tv_usec; return JS_NewInt64(ctx, d); } /* OS dependent. d = argv[0] is in ms from 1970. Return the difference between local time and UTC time 'd' in minutes */ static int getTimezoneOffset(int64_t time) { #if defined(_WIN32) /* XXX: TODO */ return 0; #else time_t ti; struct tm tm; time /= 1000; /* convert to seconds */ if (sizeof(time_t) == 4) { /* on 32-bit systems, we need to clamp the time value to the range of `time_t`. This is better than truncating values to 32 bits and hopefully provides the same result as 64-bit implementation of localtime_r. */ if ((time_t)-1 < 0) { if (time < INT32_MIN) { time = INT32_MIN; } else if (time > INT32_MAX) { time = INT32_MAX; } } else { if (time < 0) { time = 0; } else if (time > UINT32_MAX) { time = UINT32_MAX; } } } ti = time; localtime_r(&ti, &tm); return -tm.tm_gmtoff / 60; #endif } #if 0 static JSValue js___date_getTimezoneOffset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { double dd; if (JS_ToFloat64(ctx, &dd, argv[0])) return JS_EXCEPTION; if (isnan(dd)) return __JS_NewFloat64(ctx, dd); else return JS_NewInt32(ctx, getTimezoneOffset((int64_t)dd)); } /* create a new date object */ static JSValue js___date_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj, proto; proto = js_get_prototype_from_ctor(ctx, argv[0], argv[1]); if (JS_IsException(proto)) return proto; obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_DATE); JS_FreeValue(ctx, proto); if (!JS_IsException(obj)) JS_SetObjectData(ctx, obj, JS_DupValue(ctx, argv[2])); return obj; } #endif /* 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_IsUndefined(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 '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_UTF16; break; case 'y': mask = LRE_FLAG_STICKY; break; default: goto bad_flags; } if ((re_flags & mask) != 0) { bad_flags: JS_FreeCString(ctx, str); return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); } re_flags |= mask; } JS_FreeCString(ctx, str); } str = JS_ToCStringLen2(ctx, &len, pattern, !(re_flags & LRE_FLAG_UTF16)); 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(ctx, 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_IsUndefined(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_IsUndefined(new_target)) { /* called as a function */ new_target = JS_GetActiveFunction(ctx); if (pat_is_regexp && JS_IsUndefined(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_IsUndefined(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_UNDEFINED; if (pat_is_regexp) { pattern = JS_GetProperty(ctx, pat, JS_ATOM_source); if (JS_IsException(pattern)) goto fail; if (JS_IsUndefined(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_IsUndefined(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_IsUndefined(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_UNDEFINED; if (JS_IsUndefined(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; } #if 0 static JSValue js_regexp_get___source(JSContext *ctx, JSValueConst this_val) { JSRegExp *re = js_get_regexp(ctx, this_val, TRUE); if (!re) return JS_EXCEPTION; return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern)); } static JSValue js_regexp_get___flags(JSContext *ctx, JSValueConst this_val) { JSRegExp *re = js_get_regexp(ctx, this_val, TRUE); int flags; if (!re) return JS_EXCEPTION; flags = lre_get_flags(re->bytecode->u.str8); return JS_NewInt32(ctx, flags); } #endif 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_NewString(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); if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP])) return JS_UNDEFINED; re = js_get_regexp(ctx, this_val, TRUE); if (!re) return JS_EXCEPTION; flags = lre_get_flags(re->bytecode->u.str8); return JS_NewBool(ctx, (flags & mask) != 0); } static JSValue js_regexp_get_flags(JSContext *ctx, JSValueConst this_val) { char str[8], *p = str; int res; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_global)); if (res < 0) goto exception; if (res) *p++ = 'g'; res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "ignoreCase")); if (res < 0) goto exception; if (res) *p++ = 'i'; res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "multiline")); if (res < 0) goto exception; if (res) *p++ = 'm'; res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "dotAll")); if (res < 0) goto exception; if (res) *p++ = 's'; res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_unicode)); if (res < 0) goto exception; if (res) *p++ = 'u'; res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "sticky")); if (res < 0) goto exception; if (res) *p++ = 'y'; 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; } BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size) { JSContext *ctx = opaque; return js_check_stack_overflow(ctx, alloca_size); } 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 str_val, obj, val, groups = JS_UNDEFINED; uint8_t *re_bytecode; int ret; uint8_t **capture, *str_buf; int 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 str_val; val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex); if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val)) { JS_FreeValue(ctx, str_val); return JS_EXCEPTION; } 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); capture = NULL; if (capture_count > 0) { capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2); if (!capture) { JS_FreeValue(ctx, str_val); return JS_EXCEPTION; } } shift = str->is_wide_char; str_buf = str->u.str8; if (last_index > str->len) { ret = 2; } else { ret = lre_exec(capture, re_bytecode, str_buf, last_index, str->len, shift, ctx); } obj = JS_NULL; 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 { JS_ThrowInternalError(ctx, "out of memory in regexp execution"); goto fail; } JS_FreeValue(ctx, str_val); } 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 = NULL; if (re_flags & LRE_FLAG_NAMED_GROUPS) { uint32_t re_bytecode_len; groups = JS_NewObjectProto(ctx, JS_NULL); if (JS_IsException(groups)) goto fail; re_bytecode_len = get_u32(re_bytecode + 3); group_name_ptr = (char *)(re_bytecode + 7 + re_bytecode_len); } for(i = 0; i < capture_count; i++) { int start, end; JSValue val; if (capture[2 * i] == NULL || capture[2 * i + 1] == NULL) { val = JS_UNDEFINED; } else { start = (capture[2 * i] - str_buf) >> shift; end = (capture[2 * i + 1] - str_buf) >> shift; val = js_sub_string(ctx, str, start, end); if (JS_IsException(val)) goto fail; } if (group_name_ptr && i > 0) { if (*group_name_ptr) { if (JS_DefinePropertyValueStr(ctx, groups, group_name_ptr, JS_DupValue(ctx, val), prop_flags) < 0) { JS_FreeValue(ctx, val); goto fail; } } group_name_ptr += strlen(group_name_ptr) + 1; } if (JS_DefinePropertyValueUint32(ctx, obj, i, val, prop_flags) < 0) goto fail; } if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_groups, groups, prop_flags) < 0) goto fail; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_index, JS_NewInt32(ctx, (capture[0] - str_buf) >> shift), prop_flags) < 0) goto fail; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_input, str_val, prop_flags) < 0) goto fail1; } js_free(ctx, capture); return obj; fail: JS_FreeValue(ctx, groups); JS_FreeValue(ctx, str_val); fail1: JS_FreeValue(ctx, obj); js_free(ctx, capture); return JS_EXCEPTION; } /* delete partions 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 { 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_UTF16) || (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); } #if 0 static JSValue js_regexp___RegExpExec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_RegExpExec(ctx, argv[0], argv[1]); } static JSValue js_regexp___RegExpDelete(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_RegExpDelete(ctx, argv[0], argv[1]); } #endif static JSValue js_regexp_test(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; BOOL ret; val = JS_RegExpExec(ctx, this_val, argv[0]); if (JS_IsException(val)) return JS_EXCEPTION; ret = !JS_IsNull(val); JS_FreeValue(ctx, val); return JS_NewBool(ctx, ret); } static JSValue js_regexp_Symbol_match(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // [Symbol.match](str) JSValueConst rx = this_val; JSValue A, S, result, matchStr; int global, n, fullUnicode, isEmpty; JSString *p; if (!JS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx); A = JS_UNDEFINED; result = JS_UNDEFINED; matchStr = JS_UNDEFINED; S = JS_ToString(ctx, argv[0]); if (JS_IsException(S)) goto exception; global = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_global)); if (global < 0) goto exception; if (!global) { A = JS_RegExpExec(ctx, rx, S); } else { fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode)); if (fullUnicode < 0) goto exception; if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) goto exception; A = JS_NewArray(ctx); if (JS_IsException(A)) goto exception; n = 0; for(;;) { JS_FreeValue(ctx, result); result = JS_RegExpExec(ctx, rx, S); if (JS_IsException(result)) goto exception; if (JS_IsNull(result)) break; matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0)); if (JS_IsException(matchStr)) goto exception; isEmpty = JS_IsEmptyString(matchStr); if (JS_SetPropertyInt64(ctx, A, n++, matchStr) < 0) goto exception; if (isEmpty) { int64_t thisIndex, nextIndex; if (JS_ToLengthFree(ctx, &thisIndex, JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0) goto exception; p = JS_VALUE_GET_STRING(S); nextIndex = thisIndex + 1; if (thisIndex < p->len) nextIndex = string_advance_index(p, thisIndex, fullUnicode); if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt64(ctx, nextIndex)) < 0) goto exception; } } if (n == 0) { JS_FreeValue(ctx, A); A = JS_NULL; } } JS_FreeValue(ctx, result); JS_FreeValue(ctx, S); return A; exception: JS_FreeValue(ctx, A); JS_FreeValue(ctx, result); JS_FreeValue(ctx, S); return JS_EXCEPTION; } typedef struct JSRegExpStringIteratorData { JSValue iterating_regexp; JSValue iterated_string; BOOL global; BOOL unicode; BOOL done; } JSRegExpStringIteratorData; static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data; if (it) { JS_FreeValueRT(rt, it->iterating_regexp); JS_FreeValueRT(rt, it->iterated_string); js_free_rt(rt, it); } } static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data; if (it) { JS_MarkValue(rt, it->iterating_regexp, mark_func); JS_MarkValue(rt, it->iterated_string, mark_func); } } static JSValue js_regexp_string_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, BOOL *pdone, int magic) { JSRegExpStringIteratorData *it; JSValueConst R, S; JSValue matchStr = JS_UNDEFINED, match = JS_UNDEFINED; JSString *sp; it = JS_GetOpaque2(ctx, this_val, JS_CLASS_REGEXP_STRING_ITERATOR); if (!it) goto exception; if (it->done) { *pdone = TRUE; return JS_UNDEFINED; } R = it->iterating_regexp; S = it->iterated_string; match = JS_RegExpExec(ctx, R, S); if (JS_IsException(match)) goto exception; if (JS_IsNull(match)) { it->done = TRUE; *pdone = TRUE; return JS_UNDEFINED; } else if (it->global) { matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, match, 0)); if (JS_IsException(matchStr)) goto exception; if (JS_IsEmptyString(matchStr)) { int64_t thisIndex, nextIndex; if (JS_ToLengthFree(ctx, &thisIndex, JS_GetProperty(ctx, R, JS_ATOM_lastIndex)) < 0) goto exception; sp = JS_VALUE_GET_STRING(S); nextIndex = string_advance_index(sp, thisIndex, it->unicode); if (JS_SetProperty(ctx, R, JS_ATOM_lastIndex, JS_NewInt32(ctx, nextIndex)) < 0) goto exception; } JS_FreeValue(ctx, matchStr); } else { it->done = TRUE; } *pdone = FALSE; return match; exception: JS_FreeValue(ctx, match); JS_FreeValue(ctx, matchStr); *pdone = FALSE; return JS_EXCEPTION; } static JSValue js_regexp_Symbol_matchAll(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // [Symbol.matchAll](str) JSValueConst R = this_val; JSValue S, C, flags, matcher, iter; JSValueConst args[2]; JSString *strp; int64_t lastIndex; JSRegExpStringIteratorData *it; if (!JS_IsObject(R)) return JS_ThrowTypeErrorNotAnObject(ctx); C = JS_UNDEFINED; flags = JS_UNDEFINED; matcher = JS_UNDEFINED; iter = JS_UNDEFINED; S = JS_ToString(ctx, argv[0]); if (JS_IsException(S)) goto exception; C = JS_SpeciesConstructor(ctx, R, ctx->regexp_ctor); if (JS_IsException(C)) goto exception; flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, R, JS_ATOM_flags)); if (JS_IsException(flags)) goto exception; args[0] = R; args[1] = flags; matcher = JS_CallConstructor(ctx, C, 2, args); if (JS_IsException(matcher)) goto exception; if (JS_ToLengthFree(ctx, &lastIndex, JS_GetProperty(ctx, R, JS_ATOM_lastIndex))) goto exception; if (JS_SetProperty(ctx, matcher, JS_ATOM_lastIndex, JS_NewInt32(ctx, lastIndex)) < 0) goto exception; iter = JS_NewObjectClass(ctx, JS_CLASS_REGEXP_STRING_ITERATOR); if (JS_IsException(iter)) goto exception; it = js_malloc(ctx, sizeof(*it)); if (!it) goto exception; it->iterating_regexp = matcher; it->iterated_string = S; strp = JS_VALUE_GET_STRING(flags); it->global = string_indexof_char(strp, 'g', 0) >= 0; it->unicode = string_indexof_char(strp, 'u', 0) >= 0; it->done = FALSE; JS_SetOpaque(iter, it); JS_FreeValue(ctx, C); JS_FreeValue(ctx, flags); return iter; exception: JS_FreeValue(ctx, S); JS_FreeValue(ctx, C); JS_FreeValue(ctx, flags); JS_FreeValue(ctx, matcher); JS_FreeValue(ctx, iter); return JS_EXCEPTION; } typedef struct ValueBuffer { JSContext *ctx; JSValue *arr; JSValue def[4]; int len; int size; int error_status; } ValueBuffer; static int value_buffer_init(JSContext *ctx, ValueBuffer *b) { b->ctx = ctx; b->len = 0; b->size = 4; b->error_status = 0; b->arr = b->def; return 0; } static void value_buffer_free(ValueBuffer *b) { while (b->len > 0) JS_FreeValue(b->ctx, b->arr[--b->len]); if (b->arr != b->def) js_free(b->ctx, b->arr); b->arr = b->def; b->size = 4; } static int value_buffer_append(ValueBuffer *b, JSValue val) { if (b->error_status) return -1; if (b->len >= b->size) { int new_size = (b->len + (b->len >> 1) + 31) & ~16; size_t slack; JSValue *new_arr; if (b->arr == b->def) { new_arr = js_realloc2(b->ctx, NULL, sizeof(*b->arr) * new_size, &slack); if (new_arr) memcpy(new_arr, b->def, sizeof b->def); } else { new_arr = js_realloc2(b->ctx, b->arr, sizeof(*b->arr) * new_size, &slack); } if (!new_arr) { value_buffer_free(b); JS_FreeValue(b->ctx, val); b->error_status = -1; return -1; } new_size += slack / sizeof(*new_arr); b->arr = new_arr; b->size = new_size; } b->arr[b->len++] = val; return 0; } 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 JSValue js_regexp_Symbol_replace(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // [Symbol.replace](str, rep) JSValueConst rx = this_val, rep = argv[1]; JSValueConst args[6]; JSValue str, rep_val, matched, tab, rep_str, namedCaptures, res; JSString *sp, *rp; StringBuffer b_s, *b = &b_s; ValueBuffer v_b, *results = &v_b; int nextSourcePosition, n, j, functionalReplace, is_global, fullUnicode; uint32_t nCaptures; int64_t position; if (!JS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx); string_buffer_init(ctx, b, 0); value_buffer_init(ctx, results); rep_val = JS_UNDEFINED; matched = JS_UNDEFINED; tab = JS_UNDEFINED; rep_str = JS_UNDEFINED; namedCaptures = JS_UNDEFINED; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) goto exception; sp = JS_VALUE_GET_STRING(str); rp = NULL; functionalReplace = JS_IsFunction(ctx, rep); if (!functionalReplace) { rep_val = JS_ToString(ctx, rep); if (JS_IsException(rep_val)) goto exception; rp = JS_VALUE_GET_STRING(rep_val); } fullUnicode = 0; is_global = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_global)); if (is_global < 0) goto exception; if (is_global) { fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode)); if (fullUnicode < 0) goto exception; if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) goto exception; } if (rp && rp->len == 0 && is_global && js_is_standard_regexp(ctx, rx)) { /* use faster version for simple cases */ res = JS_RegExpDelete(ctx, rx, str); goto done; } for(;;) { JSValue result; result = JS_RegExpExec(ctx, rx, str); if (JS_IsException(result)) goto exception; if (JS_IsNull(result)) break; if (value_buffer_append(results, result) < 0) goto exception; if (!is_global) break; JS_FreeValue(ctx, matched); matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0)); if (JS_IsException(matched)) goto exception; if (JS_IsEmptyString(matched)) { /* always advance of at least one char */ int64_t thisIndex, nextIndex; if (JS_ToLengthFree(ctx, &thisIndex, JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0) goto exception; nextIndex = string_advance_index(sp, thisIndex, fullUnicode); if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, nextIndex)) < 0) goto exception; } } nextSourcePosition = 0; for(j = 0; j < results->len; j++) { JSValueConst result; result = results->arr[j]; if (js_get_length32(ctx, &nCaptures, result) < 0) goto exception; JS_FreeValue(ctx, matched); matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0)); if (JS_IsException(matched)) goto exception; if (JS_ToLengthFree(ctx, &position, JS_GetProperty(ctx, result, JS_ATOM_index))) goto exception; if (position > sp->len) position = sp->len; else if (position < 0) position = 0; /* ignore substition if going backward (can happen with custom regexp object) */ JS_FreeValue(ctx, tab); tab = JS_NewArray(ctx); if (JS_IsException(tab)) goto exception; if (JS_SetPropertyInt64(ctx, tab, 0, JS_DupValue(ctx, matched)) < 0) goto exception; for(n = 1; n < nCaptures; n++) { JSValue capN; capN = JS_GetPropertyInt64(ctx, result, n); if (JS_IsException(capN)) goto exception; if (!JS_IsUndefined(capN)) { capN = JS_ToStringFree(ctx, capN); if (JS_IsException(capN)) goto exception; } if (JS_SetPropertyInt64(ctx, tab, n, capN) < 0) goto exception; } JS_FreeValue(ctx, namedCaptures); namedCaptures = JS_GetProperty(ctx, result, JS_ATOM_groups); if (JS_IsException(namedCaptures)) goto exception; if (functionalReplace) { if (JS_SetPropertyInt64(ctx, tab, n++, JS_NewInt32(ctx, position)) < 0) goto exception; if (JS_SetPropertyInt64(ctx, tab, n++, JS_DupValue(ctx, str)) < 0) goto exception; if (!JS_IsUndefined(namedCaptures)) { if (JS_SetPropertyInt64(ctx, tab, n++, JS_DupValue(ctx, namedCaptures)) < 0) goto exception; } args[0] = JS_UNDEFINED; args[1] = tab; JS_FreeValue(ctx, rep_str); rep_str = JS_ToStringFree(ctx, js_function_apply(ctx, rep, 2, args, 0)); } else { args[0] = matched; args[1] = str; args[2] = JS_NewInt32(ctx, position); args[3] = tab; args[4] = namedCaptures; args[5] = rep_val; JS_FreeValue(ctx, rep_str); rep_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args); } if (JS_IsException(rep_str)) goto exception; if (position >= nextSourcePosition) { string_buffer_concat(b, sp, nextSourcePosition, position); string_buffer_concat_value(b, rep_str); nextSourcePosition = position + JS_VALUE_GET_STRING(matched)->len; } } string_buffer_concat(b, sp, nextSourcePosition, sp->len); res = string_buffer_end(b); goto done1; exception: res = JS_EXCEPTION; done: string_buffer_free(b); done1: value_buffer_free(results); JS_FreeValue(ctx, rep_val); JS_FreeValue(ctx, matched); JS_FreeValue(ctx, tab); JS_FreeValue(ctx, rep_str); JS_FreeValue(ctx, namedCaptures); JS_FreeValue(ctx, str); return res; } static JSValue js_regexp_Symbol_search(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst rx = this_val; JSValue str, previousLastIndex, currentLastIndex, result, index; if (!JS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx); result = JS_UNDEFINED; currentLastIndex = JS_UNDEFINED; previousLastIndex = JS_UNDEFINED; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) goto exception; previousLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex); if (JS_IsException(previousLastIndex)) goto exception; if (!js_same_value(ctx, previousLastIndex, JS_NewInt32(ctx, 0))) { if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, JS_NewInt32(ctx, 0)) < 0) { goto exception; } } result = JS_RegExpExec(ctx, rx, str); if (JS_IsException(result)) goto exception; currentLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex); if (JS_IsException(currentLastIndex)) goto exception; if (js_same_value(ctx, currentLastIndex, previousLastIndex)) { JS_FreeValue(ctx, previousLastIndex); } else { if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, previousLastIndex) < 0) goto exception; } JS_FreeValue(ctx, str); JS_FreeValue(ctx, currentLastIndex); if (JS_IsNull(result)) { return JS_NewInt32(ctx, -1); } else { index = JS_GetProperty(ctx, result, JS_ATOM_index); JS_FreeValue(ctx, result); return index; } exception: JS_FreeValue(ctx, result); JS_FreeValue(ctx, str); JS_FreeValue(ctx, currentLastIndex); JS_FreeValue(ctx, previousLastIndex); return JS_EXCEPTION; } static JSValue js_regexp_Symbol_split(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // [Symbol.split](str, limit) JSValueConst rx = this_val; JSValueConst args[2]; JSValue str, ctor, splitter, A, flags, z, sub; JSString *strp; uint32_t lim, size, p, q; int unicodeMatching; int64_t lengthA, e, numberOfCaptures, i; if (!JS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx); ctor = JS_UNDEFINED; splitter = JS_UNDEFINED; A = JS_UNDEFINED; flags = JS_UNDEFINED; z = JS_UNDEFINED; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) goto exception; ctor = JS_SpeciesConstructor(ctx, rx, ctx->regexp_ctor); if (JS_IsException(ctor)) goto exception; flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_flags)); if (JS_IsException(flags)) goto exception; strp = JS_VALUE_GET_STRING(flags); unicodeMatching = string_indexof_char(strp, 'u', 0) >= 0; if (string_indexof_char(strp, 'y', 0) < 0) { flags = JS_ConcatString3(ctx, "", flags, "y"); if (JS_IsException(flags)) goto exception; } args[0] = rx; args[1] = flags; splitter = JS_CallConstructor(ctx, ctor, 2, args); if (JS_IsException(splitter)) goto exception; A = JS_NewArray(ctx); if (JS_IsException(A)) goto exception; lengthA = 0; if (JS_IsUndefined(argv[1])) { lim = 0xffffffff; } else { if (JS_ToUint32(ctx, &lim, argv[1]) < 0) goto exception; if (lim == 0) goto done; } strp = JS_VALUE_GET_STRING(str); p = q = 0; size = strp->len; if (size == 0) { z = JS_RegExpExec(ctx, splitter, str); if (JS_IsException(z)) goto exception; if (JS_IsNull(z)) goto add_tail; goto done; } while (q < size) { if (JS_SetProperty(ctx, splitter, JS_ATOM_lastIndex, JS_NewInt32(ctx, q)) < 0) goto exception; JS_FreeValue(ctx, z); z = JS_RegExpExec(ctx, splitter, str); if (JS_IsException(z)) goto exception; if (JS_IsNull(z)) { q = string_advance_index(strp, q, unicodeMatching); } else { if (JS_ToLengthFree(ctx, &e, JS_GetProperty(ctx, splitter, JS_ATOM_lastIndex))) goto exception; if (e > size) e = size; if (e == p) { q = string_advance_index(strp, q, unicodeMatching); } else { sub = js_sub_string(ctx, strp, p, q); if (JS_IsException(sub)) goto exception; if (JS_SetPropertyInt64(ctx, A, lengthA++, sub) < 0) goto exception; if (lengthA == lim) goto done; p = e; if (js_get_length64(ctx, &numberOfCaptures, z)) goto exception; for(i = 1; i < numberOfCaptures; i++) { sub = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, z, i)); if (JS_IsException(sub)) goto exception; if (JS_SetPropertyInt64(ctx, A, lengthA++, sub) < 0) goto exception; if (lengthA == lim) goto done; } q = p; } } } add_tail: if (p > size) p = size; sub = js_sub_string(ctx, strp, p, size); if (JS_IsException(sub)) goto exception; if (JS_SetPropertyInt64(ctx, A, lengthA++, sub) < 0) goto exception; goto done; exception: JS_FreeValue(ctx, A); A = JS_EXCEPTION; done: JS_FreeValue(ctx, str); JS_FreeValue(ctx, ctor); JS_FreeValue(ctx, splitter); JS_FreeValue(ctx, flags); JS_FreeValue(ctx, z); return A; } static const JSCFunctionListEntry js_regexp_funcs[] = { JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), //JS_CFUNC_DEF("__RegExpExec", 2, js_regexp___RegExpExec ), //JS_CFUNC_DEF("__RegExpDelete", 2, js_regexp___RegExpDelete ), }; 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, 1 ), JS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, 2 ), JS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, 4 ), JS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, 8 ), JS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, 16 ), JS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, 32 ), JS_CFUNC_DEF("exec", 1, js_regexp_exec ), JS_CFUNC_DEF("compile", 2, js_regexp_compile ), JS_CFUNC_DEF("test", 1, js_regexp_test ), JS_CFUNC_DEF("toString", 0, js_regexp_toString ), JS_CFUNC_DEF("[Symbol.replace]", 2, js_regexp_Symbol_replace ), JS_CFUNC_DEF("[Symbol.match]", 1, js_regexp_Symbol_match ), JS_CFUNC_DEF("[Symbol.matchAll]", 1, js_regexp_Symbol_matchAll ), JS_CFUNC_DEF("[Symbol.search]", 1, js_regexp_Symbol_search ), JS_CFUNC_DEF("[Symbol.split]", 2, js_regexp_Symbol_split ), //JS_CGETSET_DEF("__source", js_regexp_get___source, NULL ), //JS_CGETSET_DEF("__flags", js_regexp_get___flags, NULL ), }; static const JSCFunctionListEntry js_regexp_string_iterator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 0, js_regexp_string_iterator_next, 0 ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "RegExp String Iterator", JS_PROP_CONFIGURABLE ), }; 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); JS_SetPropertyFunctionList(ctx, obj, js_regexp_funcs, countof(js_regexp_funcs)); ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR], js_regexp_string_iterator_proto_funcs, countof(js_regexp_string_iterator_proto_funcs)); } /* JSON */ /* XXX: this parser is less strict than the JSON standard because we reuse the Javascript tokenizer. It could be improved by adding a specific JSON parse flag. */ static JSValue json_parse_value(JSParseState *s) { JSContext *ctx = s->ctx; JSValue val = JS_NULL; BOOL is_neg; int ret; switch(s->token.val) { case '{': { JSValue prop_val, prop_str; if (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) { js_parse_error(s, "expecting property name"); goto fail; } prop_str = JS_DupValue(ctx, s->token.u.str.str); if (next_token(s)) { JS_FreeValue(ctx, prop_str); goto fail; } if (js_parse_expect(s, ':')) { JS_FreeValue(ctx, prop_str); goto fail; } prop_val = json_parse_value(s); if (JS_IsException(prop_val)) { JS_FreeValue(ctx, prop_str); goto fail; } ret = JS_DefinePropertyValueValue(ctx, val, prop_str, prop_val, JS_PROP_C_W_E); if (ret < 0) goto fail; if (s->token.val != ',') break; if (next_token(s)) goto fail; } } if (js_parse_expect(s, '}')) goto fail; } break; case '[': { JSValue el; uint32_t idx; if (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 (next_token(s)) goto fail; idx++; } } if (js_parse_expect(s, ']')) goto fail; } break; case TOK_STRING: val = JS_DupValue(ctx, s->token.u.str.str); if (next_token(s)) goto fail; break; case TOK_NUMBER: is_neg = 0; goto number; case '-': if (next_token(s)) goto fail; if (s->token.val != TOK_NUMBER) { js_parse_error(s, "number expected"); goto fail; } is_neg = 1; number: val = s->token.u.num.val; if (is_neg) { double d; JS_ToFloat64(ctx, &d, val); /* no exception possible */ val = JS_NewFloat64(ctx, -d); } if (next_token(s)) goto fail; break; case TOK_FALSE: case TOK_TRUE: val = JS_NewBool(ctx, s->token.val - TOK_FALSE); if (next_token(s)) goto fail; break; case TOK_NULL: if (next_token(s)) goto fail; break; default: if (s->token.val == TOK_EOF) { js_parse_error(s, "unexpected end of 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_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename) { JSParseState s1, *s = &s1; JSValue val; js_parse_init(ctx, s, buf, buf_len, filename); if (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: free_token(s, &s->token); return JS_EXCEPTION; } 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, 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_IsUndefined(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_free_prop_enum(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_free_prop_enum(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]; if (JS_IsObject(val) #ifdef CONFIG_BIGNUM || JS_IsBigInt(ctx, val) /* XXX: probably useless */ #endif ) { 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_IsUndefined(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_INT: case JS_TAG_FLOAT64: #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: #endif case JS_TAG_BOOL: case JS_TAG_NULL: #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: #endif case JS_TAG_EXCEPTION: return val; default: break; } JS_FreeValue(ctx, val); return JS_UNDEFINED; 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_UNDEFINED; sep = JS_UNDEFINED; sep1 = JS_UNDEFINED; tab = JS_UNDEFINED; prop = JS_UNDEFINED; switch (JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_OBJECT: 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; val = JS_ToQuotedStringFree(ctx, val); if (JS_IsException(val)) goto exception; return string_buffer_concat_value_free(jsc->b, val); } else if (cl == JS_CLASS_NUMBER) { val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) goto exception; return string_buffer_concat_value_free(jsc->b, val); } else if (cl == JS_CLASS_BOOLEAN) { ret = string_buffer_concat_value(jsc->b, p->u.object_data); JS_FreeValue(ctx, val); return ret; } #ifdef CONFIG_BIGNUM else if (cl == JS_CLASS_BIG_FLOAT) { return string_buffer_concat_value_free(jsc->b, val); } else if (cl == JS_CLASS_BIG_INT) { JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify"); goto exception; } #endif 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_NewString(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_UNDEFINED; if (JS_IsException(v)) goto exception; if (JS_IsUndefined(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_IsUndefined(jsc->property_list)) tab = JS_DupValue(ctx, jsc->property_list); else tab = js_object_keys(ctx, JS_UNDEFINED, 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_IsUndefined(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_VALUE_GET_STRING(jsc->gap)->len != 0) { 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; case JS_TAG_STRING: 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: #ifdef CONFIG_BIGNUM case JS_TAG_BIG_FLOAT: #endif case JS_TAG_BOOL: case JS_TAG_NULL: concat_value: return string_buffer_concat_value_free(jsc->b, val); #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify"); goto exception; #endif 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_UNDEFINED; jsc->stack = JS_UNDEFINED; jsc->property_list = JS_UNDEFINED; jsc->gap = JS_UNDEFINED; jsc->b = &b_s; jsc->empty = JS_AtomToString(ctx, JS_ATOM_empty_string); ret = JS_UNDEFINED; wrapper = JS_UNDEFINED; 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_NewStringLen(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_IsUndefined(val)) { ret = JS_UNDEFINED; 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)); } /* Reflect */ static JSValue js_reflect_apply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_function_apply(ctx, argv[0], max_int(0, argc - 1), argv + 1, 0); } static JSValue js_reflect_construct(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst func, array_arg, new_target; JSValue *tab, ret; uint32_t len; func = argv[0]; array_arg = argv[1]; if (argc > 2) { new_target = argv[2]; if (!JS_IsConstructor(ctx, new_target)) return JS_ThrowTypeError(ctx, "not a constructor"); } else { new_target = func; } tab = build_arg_list(ctx, &len, array_arg); if (!tab) return JS_EXCEPTION; ret = JS_CallConstructor2(ctx, func, new_target, len, (JSValueConst *)tab); free_arg_list(ctx, tab, len); return ret; } static JSValue js_reflect_deleteProperty(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst obj; JSAtom atom; int ret; obj = argv[0]; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); atom = JS_ValueToAtom(ctx, argv[1]); if (unlikely(atom == JS_ATOM_NULL)) return JS_EXCEPTION; ret = JS_DeleteProperty(ctx, obj, atom, 0); JS_FreeAtom(ctx, atom); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_reflect_get(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst obj, prop, receiver; JSAtom atom; JSValue ret; obj = argv[0]; prop = argv[1]; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); if (argc > 2) receiver = argv[2]; else receiver = obj; atom = JS_ValueToAtom(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) return JS_EXCEPTION; ret = JS_GetPropertyInternal(ctx, obj, atom, receiver, FALSE); JS_FreeAtom(ctx, atom); return ret; } static JSValue js_reflect_has(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst obj, prop; JSAtom atom; int ret; obj = argv[0]; prop = argv[1]; 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; ret = JS_HasProperty(ctx, obj, atom); JS_FreeAtom(ctx, atom); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_reflect_set(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst obj, prop, val, receiver; int ret; JSAtom atom; obj = argv[0]; prop = argv[1]; val = argv[2]; if (argc > 3) receiver = argv[3]; else receiver = obj; 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; ret = JS_SetPropertyGeneric(ctx, JS_VALUE_GET_OBJ(obj), atom, JS_DupValue(ctx, val), receiver, 0); JS_FreeAtom(ctx, atom); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_reflect_setPrototypeOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int ret; ret = JS_SetPrototypeInternal(ctx, argv[0], argv[1], FALSE); if (ret < 0) return JS_EXCEPTION; else return JS_NewBool(ctx, ret); } static JSValue js_reflect_ownKeys(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); return JS_GetOwnPropertyNames2(ctx, argv[0], JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK, JS_ITERATOR_KIND_KEY); } static const JSCFunctionListEntry js_reflect_funcs[] = { JS_CFUNC_DEF("apply", 3, js_reflect_apply ), JS_CFUNC_DEF("construct", 2, js_reflect_construct ), JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 1 ), JS_CFUNC_DEF("deleteProperty", 2, js_reflect_deleteProperty ), JS_CFUNC_DEF("get", 2, js_reflect_get ), JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 1 ), JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 1 ), JS_CFUNC_DEF("has", 2, js_reflect_has ), JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 1 ), JS_CFUNC_DEF("ownKeys", 1, js_reflect_ownKeys ), JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 1 ), JS_CFUNC_DEF("set", 3, js_reflect_set ), JS_CFUNC_DEF("setPrototypeOf", 2, js_reflect_setPrototypeOf ), }; static const JSCFunctionListEntry js_reflect_obj[] = { JS_OBJECT_DEF("Reflect", js_reflect_funcs, countof(js_reflect_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; /* Proxy */ static void js_proxy_finalizer(JSRuntime *rt, JSValue val) { JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY); if (s) { JS_FreeValueRT(rt, s->target); JS_FreeValueRT(rt, s->handler); JS_FreeValueRT(rt, s->proto); js_free_rt(rt, s); } } static void js_proxy_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY); if (s) { JS_MarkValue(rt, s->target, mark_func); JS_MarkValue(rt, s->handler, mark_func); JS_MarkValue(rt, s->proto, mark_func); } } static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx) { return JS_ThrowTypeError(ctx, "revoked proxy"); } static JSProxyData *get_proxy_method(JSContext *ctx, JSValue *pmethod, JSValueConst obj, JSAtom name) { JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY); JSValue method; /* safer to test recursion in all proxy methods */ if (js_check_stack_overflow(ctx, 0)) { JS_ThrowStackOverflow(ctx); return NULL; } /* 's' should never be NULL */ if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); return NULL; } method = JS_GetProperty(ctx, s->handler, name); if (JS_IsException(method)) return NULL; if (JS_IsNull(method)) method = JS_UNDEFINED; *pmethod = method; return s; } static JSValueConst js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret; JSValueConst proto1; int res; /* must check for timeout to avoid infinite loop in instanceof */ if (js_poll_interrupts(ctx)) return JS_EXCEPTION; s = get_proxy_method(ctx, &method, obj, JS_ATOM_getPrototypeOf); if (!s) return JS_EXCEPTION; if (JS_IsUndefined(method)) return JS_GetPrototype(ctx, s->target); ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(ret)) return ret; if (JS_VALUE_GET_TAG(ret) != JS_TAG_NULL && JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { goto fail; } res = JS_IsExtensible(ctx, s->target); if (res < 0) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } if (!res) { /* check invariant */ proto1 = JS_GetPrototype(ctx, s->target); if (JS_IsException(proto1)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } if (JS_VALUE_GET_OBJ(proto1) != JS_VALUE_GET_OBJ(ret)) { fail: JS_FreeValue(ctx, ret); return JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); } } /* store the prototype in the proxy so that its refcount is at least 1 */ set_value(ctx, &s->proto, ret); return (JSValueConst)ret; } static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, JSValueConst proto_val, BOOL throw_flag) { JSProxyData *s; JSValue method, ret; JSValueConst args[2], proto1; BOOL res; int res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_setPrototypeOf); if (!s) return -1; if (JS_IsUndefined(method)) return JS_SetPrototypeInternal(ctx, s->target, proto_val, throw_flag); args[0] = s->target; args[1] = proto_val; ret = JS_CallFree(ctx, method, s->handler, 2, args); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); if (!res) { if (throw_flag) { JS_ThrowTypeError(ctx, "proxy: bad prototype"); return -1; } else { return FALSE; } } res2 = JS_IsExtensible(ctx, s->target); if (res2 < 0) return -1; if (!res2) { proto1 = JS_GetPrototype(ctx, s->target); if (JS_IsException(proto1)) return -1; if (JS_VALUE_GET_OBJ(proto_val) != JS_VALUE_GET_OBJ(proto1)) { JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); return -1; } } return TRUE; } static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret; BOOL res; int res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_isExtensible); if (!s) return -1; if (JS_IsUndefined(method)) return JS_IsExtensible(ctx, s->target); ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); res2 = JS_IsExtensible(ctx, s->target); if (res2 < 0) return res2; if (res != res2) { JS_ThrowTypeError(ctx, "proxy: inconsistent isExtensible"); return -1; } return res; } static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj) { JSProxyData *s; JSValue method, ret; BOOL res; int res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_preventExtensions); if (!s) return -1; if (JS_IsUndefined(method)) return JS_PreventExtensions(ctx, s->target); ret = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); if (res) { res2 = JS_IsExtensible(ctx, s->target); if (res2 < 0) return res2; if (res2) { JS_ThrowTypeError(ctx, "proxy: inconsistent preventExtensions"); return -1; } } return res; } static int js_proxy_has(JSContext *ctx, JSValueConst obj, JSAtom atom) { JSProxyData *s; JSValue method, ret1, atom_val; int ret, res; JSObject *p; JSValueConst args[2]; BOOL res2; s = get_proxy_method(ctx, &method, obj, JS_ATOM_has); if (!s) return -1; if (JS_IsUndefined(method)) return JS_HasProperty(ctx, s->target, atom); atom_val = JS_AtomToValue(ctx, atom); if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = atom_val; ret1 = JS_CallFree(ctx, method, s->handler, 2, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret1)) return -1; ret = JS_ToBoolFree(ctx, ret1); if (!ret) { JSPropertyDescriptor desc; p = JS_VALUE_GET_OBJ(s->target); res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); if (res < 0) return -1; if (res) { res2 = !(desc.flags & JS_PROP_CONFIGURABLE); js_free_desc(ctx, &desc); if (res2 || !p->extensible) { JS_ThrowTypeError(ctx, "proxy: inconsistent has"); return -1; } } } return ret; } static JSValue js_proxy_get(JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { JSProxyData *s; JSValue method, ret, atom_val; int res; JSValueConst args[3]; JSPropertyDescriptor desc; s = get_proxy_method(ctx, &method, obj, JS_ATOM_get); if (!s) return JS_EXCEPTION; /* Note: recursion is possible thru the prototype of s->target */ if (JS_IsUndefined(method)) return JS_GetPropertyInternal(ctx, s->target, atom, receiver, FALSE); atom_val = JS_AtomToValue(ctx, atom); if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return JS_EXCEPTION; } args[0] = s->target; args[1] = atom_val; args[2] = receiver; ret = JS_CallFree(ctx, method, s->handler, 3, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret)) return JS_EXCEPTION; res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); if (res < 0) return JS_EXCEPTION; if (res) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) { if (!js_same_value(ctx, desc.value, ret)) { goto fail; } } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET) { if (JS_IsUndefined(desc.getter) && !JS_IsUndefined(ret)) { fail: js_free_desc(ctx, &desc); JS_FreeValue(ctx, ret); return JS_ThrowTypeError(ctx, "proxy: inconsistent get"); } } js_free_desc(ctx, &desc); } return ret; } static int js_proxy_set(JSContext *ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { JSProxyData *s; JSValue method, ret1, atom_val; int ret, res; JSValueConst args[4]; s = get_proxy_method(ctx, &method, obj, JS_ATOM_set); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_SetPropertyGeneric(ctx, JS_VALUE_GET_OBJ(s->target), atom, JS_DupValue(ctx, value), receiver, flags); } atom_val = JS_AtomToValue(ctx, atom); if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = atom_val; args[2] = value; args[3] = receiver; ret1 = JS_CallFree(ctx, method, s->handler, 4, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret1)) return -1; ret = JS_ToBoolFree(ctx, ret1); if (ret) { JSPropertyDescriptor desc; res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); if (res < 0) return -1; if (res) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) { if (!js_same_value(ctx, desc.value, value)) { goto fail; } } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET && JS_IsUndefined(desc.setter)) { fail: js_free_desc(ctx, &desc); JS_ThrowTypeError(ctx, "proxy: inconsistent set"); return -1; } js_free_desc(ctx, &desc); } } else { if ((flags & JS_PROP_THROW) || ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { JS_ThrowTypeError(ctx, "proxy: cannot set property"); return -1; } } return ret; } static JSValue js_create_desc(JSContext *ctx, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { JSValue ret; ret = JS_NewObject(ctx); if (JS_IsException(ret)) return ret; if (flags & JS_PROP_HAS_GET) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, JS_DupValue(ctx, getter), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_SET) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, JS_DupValue(ctx, setter), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_VALUE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, JS_DupValue(ctx, val), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_WRITABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable, JS_NewBool(ctx, (flags & JS_PROP_WRITABLE) != 0), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_ENUMERABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable, JS_NewBool(ctx, (flags & JS_PROP_ENUMERABLE) != 0), JS_PROP_C_W_E); } if (flags & JS_PROP_HAS_CONFIGURABLE) { JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable, JS_NewBool(ctx, (flags & JS_PROP_CONFIGURABLE) != 0), JS_PROP_C_W_E); } return ret; } static int js_proxy_get_own_property(JSContext *ctx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) { JSProxyData *s; JSValue method, trap_result_obj, prop_val; int res, target_desc_ret, ret; JSObject *p; JSValueConst args[2]; JSPropertyDescriptor result_desc, target_desc; s = get_proxy_method(ctx, &method, obj, JS_ATOM_getOwnPropertyDescriptor); if (!s) return -1; p = JS_VALUE_GET_OBJ(s->target); if (JS_IsUndefined(method)) { return JS_GetOwnPropertyInternal(ctx, pdesc, p, prop); } prop_val = JS_AtomToValue(ctx, prop); if (JS_IsException(prop_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = prop_val; trap_result_obj = JS_CallFree(ctx, method, s->handler, 2, args); JS_FreeValue(ctx, prop_val); if (JS_IsException(trap_result_obj)) return -1; if (!JS_IsObject(trap_result_obj) && !JS_IsUndefined(trap_result_obj)) { JS_FreeValue(ctx, trap_result_obj); goto fail; } target_desc_ret = JS_GetOwnPropertyInternal(ctx, &target_desc, p, prop); if (target_desc_ret < 0) { JS_FreeValue(ctx, trap_result_obj); return -1; } if (target_desc_ret) js_free_desc(ctx, &target_desc); if (JS_IsUndefined(trap_result_obj)) { if (target_desc_ret) { if (!(target_desc.flags & JS_PROP_CONFIGURABLE) || !p->extensible) goto fail; } ret = FALSE; } else { int flags1, extensible_target; extensible_target = JS_IsExtensible(ctx, s->target); if (extensible_target < 0) { JS_FreeValue(ctx, trap_result_obj); return -1; } res = js_obj_to_desc(ctx, &result_desc, trap_result_obj); JS_FreeValue(ctx, trap_result_obj); if (res < 0) return -1; if (target_desc_ret) { /* convert result_desc.flags to defineProperty flags */ flags1 = result_desc.flags | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE; if (result_desc.flags & JS_PROP_GETSET) flags1 |= JS_PROP_HAS_GET | JS_PROP_HAS_SET; else flags1 |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE; /* XXX: not complete check: need to compare value & getter/setter as in defineproperty */ if (!check_define_prop_flags(target_desc.flags, flags1)) goto fail1; } else { if (!extensible_target) goto fail1; } if (!(result_desc.flags & JS_PROP_CONFIGURABLE)) { if (!target_desc_ret || (target_desc.flags & JS_PROP_CONFIGURABLE)) goto fail1; if ((result_desc.flags & (JS_PROP_GETSET | JS_PROP_WRITABLE)) == 0 && target_desc_ret && (target_desc.flags & JS_PROP_WRITABLE) != 0) { /* proxy-missing-checks */ fail1: js_free_desc(ctx, &result_desc); fail: JS_ThrowTypeError(ctx, "proxy: inconsistent getOwnPropertyDescriptor"); return -1; } } ret = TRUE; if (pdesc) { *pdesc = result_desc; } else { js_free_desc(ctx, &result_desc); } } return ret; } static int js_proxy_define_own_property(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValueConst val, JSValueConst getter, JSValueConst setter, int flags) { JSProxyData *s; JSValue method, ret1, prop_val, desc_val; int res, ret; JSObject *p; JSValueConst args[3]; JSPropertyDescriptor desc; BOOL setting_not_configurable; s = get_proxy_method(ctx, &method, obj, JS_ATOM_defineProperty); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_DefineProperty(ctx, s->target, prop, val, getter, setter, flags); } prop_val = JS_AtomToValue(ctx, prop); if (JS_IsException(prop_val)) { JS_FreeValue(ctx, method); return -1; } desc_val = js_create_desc(ctx, val, getter, setter, flags); if (JS_IsException(desc_val)) { JS_FreeValue(ctx, prop_val); JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = prop_val; args[2] = desc_val; ret1 = JS_CallFree(ctx, method, s->handler, 3, args); JS_FreeValue(ctx, prop_val); JS_FreeValue(ctx, desc_val); if (JS_IsException(ret1)) return -1; ret = JS_ToBoolFree(ctx, ret1); if (!ret) { if (flags & JS_PROP_THROW) { JS_ThrowTypeError(ctx, "proxy: defineProperty exception"); return -1; } else { return 0; } } p = JS_VALUE_GET_OBJ(s->target); res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); if (res < 0) return -1; setting_not_configurable = ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == JS_PROP_HAS_CONFIGURABLE); if (!res) { if (!p->extensible || setting_not_configurable) goto fail; } else { if (!check_define_prop_flags(desc.flags, flags) || ((desc.flags & JS_PROP_CONFIGURABLE) && setting_not_configurable)) { goto fail1; } if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET) { if ((flags & JS_PROP_HAS_GET) && !js_same_value(ctx, getter, desc.getter)) { goto fail1; } if ((flags & JS_PROP_HAS_SET) && !js_same_value(ctx, setter, desc.setter)) { goto fail1; } } } else if (flags & JS_PROP_HAS_VALUE) { if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == JS_PROP_WRITABLE && !(flags & JS_PROP_WRITABLE)) { /* missing-proxy-check feature */ goto fail1; } else if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && !js_same_value(ctx, val, desc.value)) { goto fail1; } } if (flags & JS_PROP_HAS_WRITABLE) { if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == JS_PROP_WRITABLE) { /* proxy-missing-checks */ fail1: js_free_desc(ctx, &desc); fail: JS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty"); return -1; } } js_free_desc(ctx, &desc); } return 1; } static int js_proxy_delete_property(JSContext *ctx, JSValueConst obj, JSAtom atom) { JSProxyData *s; JSValue method, ret, atom_val; int res, res2, is_extensible; JSValueConst args[2]; s = get_proxy_method(ctx, &method, obj, JS_ATOM_deleteProperty); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_DeleteProperty(ctx, s->target, atom, 0); } atom_val = JS_AtomToValue(ctx, atom);; if (JS_IsException(atom_val)) { JS_FreeValue(ctx, method); return -1; } args[0] = s->target; args[1] = atom_val; ret = JS_CallFree(ctx, method, s->handler, 2, args); JS_FreeValue(ctx, atom_val); if (JS_IsException(ret)) return -1; res = JS_ToBoolFree(ctx, ret); if (res) { JSPropertyDescriptor desc; res2 = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); if (res2 < 0) return -1; if (res2) { if (!(desc.flags & JS_PROP_CONFIGURABLE)) goto fail; is_extensible = JS_IsExtensible(ctx, s->target); if (is_extensible < 0) goto fail1; if (!is_extensible) { /* proxy-missing-checks */ fail: JS_ThrowTypeError(ctx, "proxy: inconsistent deleteProperty"); fail1: js_free_desc(ctx, &desc); return -1; } js_free_desc(ctx, &desc); } } return res; } /* return the index of the property or -1 if not found */ static int find_prop_key(const JSPropertyEnum *tab, int n, JSAtom atom) { int i; for(i = 0; i < n; i++) { if (tab[i].atom == atom) return i; } return -1; } static int js_proxy_get_own_property_names(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) { JSProxyData *s; JSValue method, prop_array, val; uint32_t len, i, len2; JSPropertyEnum *tab, *tab2; JSAtom atom; JSPropertyDescriptor desc; int res, is_extensible, idx; s = get_proxy_method(ctx, &method, obj, JS_ATOM_ownKeys); if (!s) return -1; if (JS_IsUndefined(method)) { return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, JS_VALUE_GET_OBJ(s->target), JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK); } prop_array = JS_CallFree(ctx, method, s->handler, 1, (JSValueConst *)&s->target); if (JS_IsException(prop_array)) return -1; tab = NULL; len = 0; tab2 = NULL; len2 = 0; if (js_get_length32(ctx, &len, prop_array)) goto fail; if (len > 0) { tab = js_mallocz(ctx, sizeof(tab[0]) * len); if (!tab) goto fail; } for(i = 0; i < len; i++) { val = JS_GetPropertyUint32(ctx, prop_array, i); if (JS_IsException(val)) goto fail; if (!JS_IsString(val) && !JS_IsSymbol(val)) { JS_FreeValue(ctx, val); JS_ThrowTypeError(ctx, "proxy: properties must be strings or symbols"); goto fail; } atom = JS_ValueToAtom(ctx, val); JS_FreeValue(ctx, val); if (atom == JS_ATOM_NULL) goto fail; tab[i].atom = atom; tab[i].is_enumerable = FALSE; /* XXX: redundant? */ } /* check duplicate properties (XXX: inefficient, could store the * properties an a temporary object to use the hash) */ for(i = 1; i < len; i++) { if (find_prop_key(tab, i, tab[i].atom) >= 0) { JS_ThrowTypeError(ctx, "proxy: duplicate property"); goto fail; } } is_extensible = JS_IsExtensible(ctx, s->target); if (is_extensible < 0) goto fail; /* check if there are non configurable properties */ if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); goto fail; } if (JS_GetOwnPropertyNamesInternal(ctx, &tab2, &len2, JS_VALUE_GET_OBJ(s->target), JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK)) goto fail; for(i = 0; i < len2; i++) { if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); goto fail; } res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), tab2[i].atom); if (res < 0) goto fail; if (res) { /* safety, property should be found */ js_free_desc(ctx, &desc); if (!(desc.flags & JS_PROP_CONFIGURABLE) || !is_extensible) { idx = find_prop_key(tab, len, tab2[i].atom); if (idx < 0) { JS_ThrowTypeError(ctx, "proxy: target property must be present in proxy ownKeys"); goto fail; } /* mark the property as found */ if (!is_extensible) tab[idx].is_enumerable = TRUE; } } } if (!is_extensible) { /* check that all property in 'tab' were checked */ for(i = 0; i < len; i++) { if (!tab[i].is_enumerable) { JS_ThrowTypeError(ctx, "proxy: property not present in target were returned by non extensible proxy"); goto fail; } } } js_free_prop_enum(ctx, tab2, len2); JS_FreeValue(ctx, prop_array); *ptab = tab; *plen = len; return 0; fail: js_free_prop_enum(ctx, tab2, len2); js_free_prop_enum(ctx, tab, len); JS_FreeValue(ctx, prop_array); return -1; } static JSValue js_proxy_call_constructor(JSContext *ctx, JSValueConst func_obj, JSValueConst new_target, int argc, JSValueConst *argv) { JSProxyData *s; JSValue method, arg_array, ret; JSValueConst args[3]; s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_construct); if (!s) return JS_EXCEPTION; if (!JS_IsConstructor(ctx, s->target)) return JS_ThrowTypeError(ctx, "not a constructor"); if (JS_IsUndefined(method)) return JS_CallConstructor2(ctx, s->target, new_target, argc, argv); arg_array = js_create_array(ctx, argc, argv); if (JS_IsException(arg_array)) { ret = JS_EXCEPTION; goto fail; } args[0] = s->target; args[1] = arg_array; args[2] = new_target; ret = JS_Call(ctx, method, s->handler, 3, args); if (!JS_IsException(ret) && JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { JS_FreeValue(ctx, ret); ret = JS_ThrowTypeErrorNotAnObject(ctx); } fail: JS_FreeValue(ctx, method); JS_FreeValue(ctx, arg_array); return ret; } static JSValue js_proxy_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv, int flags) { JSProxyData *s; JSValue method, arg_array, ret; JSValueConst args[3]; if (flags & JS_CALL_FLAG_CONSTRUCTOR) return js_proxy_call_constructor(ctx, func_obj, this_obj, argc, argv); s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_apply); if (!s) return JS_EXCEPTION; if (!s->is_func) { JS_FreeValue(ctx, method); return JS_ThrowTypeError(ctx, "not a function"); } if (JS_IsUndefined(method)) return JS_Call(ctx, s->target, this_obj, argc, argv); arg_array = js_create_array(ctx, argc, argv); if (JS_IsException(arg_array)) { ret = JS_EXCEPTION; goto fail; } args[0] = s->target; args[1] = this_obj; args[2] = arg_array; ret = JS_Call(ctx, method, s->handler, 3, args); fail: JS_FreeValue(ctx, method); JS_FreeValue(ctx, arg_array); return ret; } static int js_proxy_isArray(JSContext *ctx, JSValueConst obj) { JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY); if (!s) return FALSE; if (s->is_revoked) { JS_ThrowTypeErrorRevokedProxy(ctx); return -1; } return JS_IsArray(ctx, s->target); } static const JSClassExoticMethods js_proxy_exotic_methods = { .get_own_property = js_proxy_get_own_property, .define_own_property = js_proxy_define_own_property, .delete_property = js_proxy_delete_property, .get_own_property_names = js_proxy_get_own_property_names, .has_property = js_proxy_has, .get_property = js_proxy_get, .set_property = js_proxy_set, }; static JSValue js_proxy_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst target, handler; JSValue obj; JSProxyData *s; target = argv[0]; handler = argv[1]; if (JS_VALUE_GET_TAG(target) != JS_TAG_OBJECT || JS_VALUE_GET_TAG(handler) != JS_TAG_OBJECT) return JS_ThrowTypeErrorNotAnObject(ctx); s = JS_GetOpaque(target, JS_CLASS_PROXY); if (s && s->is_revoked) goto revoked_proxy; s = JS_GetOpaque(handler, JS_CLASS_PROXY); if (s && s->is_revoked) { revoked_proxy: return JS_ThrowTypeErrorRevokedProxy(ctx); } obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_PROXY); if (JS_IsException(obj)) return obj; s = js_malloc(ctx, sizeof(JSProxyData)); if (!s) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } s->target = JS_DupValue(ctx, target); s->handler = JS_DupValue(ctx, handler); s->proto = JS_NULL; s->is_func = JS_IsFunction(ctx, target); s->is_revoked = FALSE; JS_SetOpaque(obj, s); JS_SetConstructorBit(ctx, obj, JS_IsConstructor(ctx, target)); return obj; } static JSValue js_proxy_revoke(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { JSProxyData *s = JS_GetOpaque(func_data[0], JS_CLASS_PROXY); if (s) { /* We do not free the handler and target in case they are referenced as constants in the C call stack */ s->is_revoked = TRUE; JS_FreeValue(ctx, func_data[0]); func_data[0] = JS_NULL; } return JS_UNDEFINED; } static JSValue js_proxy_revoke_constructor(JSContext *ctx, JSValueConst proxy_obj) { return JS_NewCFunctionData(ctx, js_proxy_revoke, 0, 0, 1, &proxy_obj); } static JSValue js_proxy_revocable(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue proxy_obj, revoke_obj = JS_UNDEFINED, obj; proxy_obj = js_proxy_constructor(ctx, JS_UNDEFINED, argc, argv); if (JS_IsException(proxy_obj)) goto fail; revoke_obj = js_proxy_revoke_constructor(ctx, proxy_obj); if (JS_IsException(revoke_obj)) goto fail; obj = JS_NewObject(ctx); if (JS_IsException(obj)) goto fail; // XXX: exceptions? JS_DefinePropertyValue(ctx, obj, JS_ATOM_proxy, proxy_obj, JS_PROP_C_W_E); JS_DefinePropertyValue(ctx, obj, JS_ATOM_revoke, revoke_obj, JS_PROP_C_W_E); return obj; fail: JS_FreeValue(ctx, proxy_obj); JS_FreeValue(ctx, revoke_obj); return JS_EXCEPTION; } static const JSCFunctionListEntry js_proxy_funcs[] = { JS_CFUNC_DEF("revocable", 2, js_proxy_revocable ), }; static const JSClassShortDef js_proxy_class_def[] = { { JS_ATOM_Object, js_proxy_finalizer, js_proxy_mark }, /* JS_CLASS_PROXY */ }; void JS_AddIntrinsicProxy(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; if (!JS_IsRegisteredClass(rt, JS_CLASS_PROXY)) { init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY, countof(js_proxy_class_def)); rt->class_array[JS_CLASS_PROXY].exotic = &js_proxy_exotic_methods; rt->class_array[JS_CLASS_PROXY].call = js_proxy_call; } obj1 = JS_NewCFunction2(ctx, js_proxy_constructor, "Proxy", 2, JS_CFUNC_constructor, 0); JS_SetConstructorBit(ctx, obj1, TRUE); JS_SetPropertyFunctionList(ctx, obj1, js_proxy_funcs, countof(js_proxy_funcs)); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "Proxy", obj1, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } /* Symbol */ static JSValue js_symbol_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue str; JSString *p; if (!JS_IsUndefined(new_target)) return JS_ThrowTypeError(ctx, "not a constructor"); if (argc == 0 || JS_IsUndefined(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_UNDEFINED, 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_UNDEFINED; } 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 ), }; static JSValue js_symbol_for(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return JS_EXCEPTION; return JS_NewSymbol(ctx, JS_VALUE_GET_STRING(str), JS_ATOM_TYPE_GLOBAL_SYMBOL); } static JSValue js_symbol_keyFor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSAtomStruct *p; if (!JS_IsSymbol(argv[0])) return JS_ThrowTypeError(ctx, "not a symbol"); p = JS_VALUE_GET_PTR(argv[0]); if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL) return JS_UNDEFINED; return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); } static const JSCFunctionListEntry js_symbol_funcs[] = { JS_CFUNC_DEF("for", 1, js_symbol_for ), JS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor ), }; /* Set/Map/WeakSet/WeakMap */ typedef struct JSMapRecord { int ref_count; /* used during enumeration to avoid freeing the record */ BOOL empty; /* TRUE if the record is deleted */ struct JSMapState *map; struct JSMapRecord *next_weak_ref; struct list_head link; struct list_head hash_link; JSValue key; JSValue value; } JSMapRecord; typedef struct JSMapState { BOOL is_weak; /* TRUE if WeakSet/WeakMap */ struct list_head records; /* list of JSMapRecord.link */ uint32_t record_count; struct list_head *hash_table; uint32_t hash_size; /* must be a power of two */ uint32_t record_count_threshold; /* count at which a hash table resize is needed */ } JSMapState; #define MAGIC_SET (1 << 0) #define MAGIC_WEAK (1 << 1) static JSValue js_map_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int magic) { JSMapState *s; JSValue obj, adder = JS_UNDEFINED, iter = JS_UNDEFINED, next_method = JS_UNDEFINED; JSValueConst arr; BOOL is_set, is_weak; is_set = magic & MAGIC_SET; is_weak = ((magic & MAGIC_WEAK) != 0); obj = js_create_from_ctor(ctx, new_target, JS_CLASS_MAP + magic); if (JS_IsException(obj)) return JS_EXCEPTION; s = js_mallocz(ctx, sizeof(*s)); if (!s) goto fail; init_list_head(&s->records); s->is_weak = is_weak; JS_SetOpaque(obj, s); s->hash_size = 1; s->hash_table = js_malloc(ctx, sizeof(s->hash_table[0]) * s->hash_size); if (!s->hash_table) goto fail; init_list_head(&s->hash_table[0]); s->record_count_threshold = 4; arr = JS_UNDEFINED; if (argc > 0) arr = argv[0]; if (!JS_IsUndefined(arr) && !JS_IsNull(arr)) { JSValue item, ret; BOOL done; adder = JS_GetProperty(ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set); if (JS_IsException(adder)) goto fail; if (!JS_IsFunction(ctx, adder)) { JS_ThrowTypeError(ctx, "set/add is not a function"); goto fail; } iter = JS_GetIterator(ctx, arr, FALSE); if (JS_IsException(iter)) goto fail; next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); if (JS_IsException(next_method)) goto fail; for(;;) { item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(item)) goto fail; if (done) { JS_FreeValue(ctx, item); break; } if (is_set) { ret = JS_Call(ctx, adder, obj, 1, (JSValueConst *)&item); if (JS_IsException(ret)) { JS_FreeValue(ctx, item); goto fail; } } else { JSValue key, value; JSValueConst args[2]; key = JS_UNDEFINED; value = JS_UNDEFINED; if (!JS_IsObject(item)) { JS_ThrowTypeErrorNotAnObject(ctx); goto fail1; } key = JS_GetPropertyUint32(ctx, item, 0); if (JS_IsException(key)) goto fail1; value = JS_GetPropertyUint32(ctx, item, 1); if (JS_IsException(value)) goto fail1; args[0] = key; args[1] = value; ret = JS_Call(ctx, adder, obj, 2, args); if (JS_IsException(ret)) { fail1: JS_FreeValue(ctx, item); JS_FreeValue(ctx, key); JS_FreeValue(ctx, value); goto fail; } JS_FreeValue(ctx, key); JS_FreeValue(ctx, value); } JS_FreeValue(ctx, ret); JS_FreeValue(ctx, item); } JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, adder); } return obj; fail: if (JS_IsObject(iter)) { /* close the iterator object, preserving pending exception */ JS_IteratorClose(ctx, iter, TRUE); } JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, adder); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } /* XXX: could normalize strings to speed up comparison */ static JSValueConst map_normalize_key(JSContext *ctx, JSValueConst key) { uint32_t tag = JS_VALUE_GET_TAG(key); /* convert -0.0 to +0.0 */ if (JS_TAG_IS_FLOAT64(tag) && JS_VALUE_GET_FLOAT64(key) == 0.0) { key = JS_NewInt32(ctx, 0); } return key; } /* XXX: better hash ? */ static uint32_t map_hash_key(JSContext *ctx, JSValueConst key) { uint32_t tag = JS_VALUE_GET_NORM_TAG(key); uint32_t h; double d; JSFloat64Union u; switch(tag) { case JS_TAG_BOOL: h = JS_VALUE_GET_INT(key); break; case JS_TAG_STRING: h = hash_string(JS_VALUE_GET_STRING(key), 0); break; case JS_TAG_OBJECT: case JS_TAG_SYMBOL: h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163; break; case JS_TAG_INT: d = JS_VALUE_GET_INT(key) * 3163; goto hash_float64; case JS_TAG_FLOAT64: d = JS_VALUE_GET_FLOAT64(key); /* normalize the NaN */ if (isnan(d)) d = JS_FLOAT64_NAN; hash_float64: u.d = d; h = (u.u32[0] ^ u.u32[1]) * 3163; break; default: h = 0; /* XXX: bignum support */ break; } h ^= tag; return h; } static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s, JSValueConst key) { struct list_head *el; JSMapRecord *mr; uint32_t h; h = map_hash_key(ctx, key) & (s->hash_size - 1); list_for_each(el, &s->hash_table[h]) { mr = list_entry(el, JSMapRecord, hash_link); if (js_same_value_zero(ctx, mr->key, key)) return mr; } return NULL; } static void map_hash_resize(JSContext *ctx, JSMapState *s) { uint32_t new_hash_size, i, h; size_t slack; struct list_head *new_hash_table, *el; JSMapRecord *mr; /* XXX: no reporting of memory allocation failure */ if (s->hash_size == 1) new_hash_size = 4; else new_hash_size = s->hash_size * 2; new_hash_table = js_realloc2(ctx, s->hash_table, sizeof(new_hash_table[0]) * new_hash_size, &slack); if (!new_hash_table) return; new_hash_size += slack / sizeof(*new_hash_table); for(i = 0; i < new_hash_size; i++) init_list_head(&new_hash_table[i]); list_for_each(el, &s->records) { mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { h = map_hash_key(ctx, mr->key) & (new_hash_size - 1); list_add_tail(&mr->hash_link, &new_hash_table[h]); } } s->hash_table = new_hash_table; s->hash_size = new_hash_size; s->record_count_threshold = new_hash_size * 2; } static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s, JSValueConst key) { uint32_t h; JSMapRecord *mr; mr = js_malloc(ctx, sizeof(*mr)); if (!mr) return NULL; mr->ref_count = 1; mr->map = s; mr->empty = FALSE; if (s->is_weak) { JSObject *p = JS_VALUE_GET_OBJ(key); /* Add the weak reference */ mr->next_weak_ref = p->first_weak_ref; p->first_weak_ref = mr; } else { JS_DupValue(ctx, key); } mr->key = (JSValue)key; h = map_hash_key(ctx, key) & (s->hash_size - 1); list_add_tail(&mr->hash_link, &s->hash_table[h]); list_add_tail(&mr->link, &s->records); s->record_count++; if (s->record_count >= s->record_count_threshold) { map_hash_resize(ctx, s); } return mr; } /* Remove the weak reference from the object weak reference list. we don't use a doubly linked list to save space, assuming a given object has few weak references to it */ static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr) { JSMapRecord **pmr, *mr1; JSObject *p; p = JS_VALUE_GET_OBJ(mr->key); pmr = &p->first_weak_ref; for(;;) { mr1 = *pmr; assert(mr1 != NULL); if (mr1 == mr) break; pmr = &mr1->next_weak_ref; } *pmr = mr1->next_weak_ref; } static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) { if (mr->empty) return; list_del(&mr->hash_link); if (s->is_weak) { delete_weak_ref(rt, mr); } else { JS_FreeValueRT(rt, mr->key); } JS_FreeValueRT(rt, mr->value); if (--mr->ref_count == 0) { list_del(&mr->link); js_free_rt(rt, mr); } else { /* keep a zombie record for iterators */ mr->empty = TRUE; mr->key = JS_UNDEFINED; mr->value = JS_UNDEFINED; } s->record_count--; } static void map_decref_record(JSRuntime *rt, JSMapRecord *mr) { if (--mr->ref_count == 0) { /* the record can be safely removed */ assert(mr->empty); list_del(&mr->link); js_free_rt(rt, mr); } } static void reset_weak_ref(JSRuntime *rt, JSObject *p) { JSMapRecord *mr, *mr_next; JSMapState *s; /* first pass to remove the records from the WeakMap/WeakSet lists */ for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) { s = mr->map; assert(s->is_weak); assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ list_del(&mr->hash_link); list_del(&mr->link); } /* second pass to free the values to avoid modifying the weak reference list while traversing it. */ for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) { mr_next = mr->next_weak_ref; JS_FreeValueRT(rt, mr->value); js_free_rt(rt, mr); } p->first_weak_ref = NULL; /* fail safe */ } static JSValue js_map_set(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key, value; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); if (s->is_weak && !JS_IsObject(key)) return JS_ThrowTypeErrorNotAnObject(ctx); if (magic & MAGIC_SET) value = JS_UNDEFINED; else value = argv[1]; mr = map_find_record(ctx, s, key); if (mr) { JS_FreeValue(ctx, mr->value); } else { mr = map_add_record(ctx, s, key); if (!mr) return JS_EXCEPTION; } mr->value = JS_DupValue(ctx, value); return JS_DupValue(ctx, this_val); } static JSValue js_map_get(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); mr = map_find_record(ctx, s, key); if (!mr) return JS_UNDEFINED; else return JS_DupValue(ctx, mr->value); } static JSValue js_map_has(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); mr = map_find_record(ctx, s, key); return JS_NewBool(ctx, (mr != NULL)); } static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); mr = map_find_record(ctx, s, key); if (!mr) return JS_FALSE; map_delete_record(ctx->rt, s, mr); return JS_TRUE; } static JSValue js_map_clear(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); struct list_head *el, *el1; JSMapRecord *mr; if (!s) return JS_EXCEPTION; list_for_each_safe(el, el1, &s->records) { mr = list_entry(el, JSMapRecord, link); map_delete_record(ctx->rt, s, mr); } return JS_UNDEFINED; } static JSValue js_map_get_size(JSContext *ctx, JSValueConst this_val, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); if (!s) return JS_EXCEPTION; return JS_NewUint32(ctx, s->record_count); } static JSValue js_map_forEach(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSValueConst func, this_arg; JSValue ret, args[3]; struct list_head *el; JSMapRecord *mr; if (!s) return JS_EXCEPTION; func = argv[0]; if (argc > 1) this_arg = argv[1]; else this_arg = JS_UNDEFINED; if (check_function(ctx, func)) return JS_EXCEPTION; /* Note: the list can be modified while traversing it, but the current element is locked */ el = s->records.next; while (el != &s->records) { mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { mr->ref_count++; /* must duplicate in case the record is deleted */ args[1] = JS_DupValue(ctx, mr->key); if (magic) args[0] = args[1]; else args[0] = JS_DupValue(ctx, mr->value); args[2] = (JSValue)this_val; ret = JS_Call(ctx, func, this_arg, 3, (JSValueConst *)args); JS_FreeValue(ctx, args[0]); if (!magic) JS_FreeValue(ctx, args[1]); el = el->next; map_decref_record(ctx->rt, mr); if (JS_IsException(ret)) return ret; JS_FreeValue(ctx, ret); } else { el = el->next; } } return JS_UNDEFINED; } static void js_map_finalizer(JSRuntime *rt, JSValue val) { JSObject *p; JSMapState *s; struct list_head *el, *el1; JSMapRecord *mr; p = JS_VALUE_GET_OBJ(val); s = p->u.map_state; if (s) { /* if the object is deleted we are sure that no iterator is using it */ list_for_each_safe(el, el1, &s->records) { mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { if (s->is_weak) delete_weak_ref(rt, mr); else JS_FreeValueRT(rt, mr->key); JS_FreeValueRT(rt, mr->value); } js_free_rt(rt, mr); } js_free_rt(rt, s->hash_table); js_free_rt(rt, s); } } static void js_map_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSMapState *s; struct list_head *el; JSMapRecord *mr; s = p->u.map_state; if (s) { list_for_each(el, &s->records) { mr = list_entry(el, JSMapRecord, link); if (!s->is_weak) JS_MarkValue(rt, mr->key, mark_func); JS_MarkValue(rt, mr->value, mark_func); } } } /* Map Iterator */ typedef struct JSMapIteratorData { JSValue obj; JSIteratorKindEnum kind; JSMapRecord *cur_record; } JSMapIteratorData; static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val) { JSObject *p; JSMapIteratorData *it; p = JS_VALUE_GET_OBJ(val); it = p->u.map_iterator_data; if (it) { /* During the GC sweep phase the Map finalizer may be called before the Map iterator finalizer */ if (JS_IsLiveObject(rt, it->obj) && it->cur_record) { map_decref_record(rt, it->cur_record); } JS_FreeValueRT(rt, it->obj); js_free_rt(rt, it); } } static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSMapIteratorData *it; it = p->u.map_iterator_data; if (it) { /* the record is already marked by the object */ JS_MarkValue(rt, it->obj, mark_func); } } static JSValue js_create_map_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSIteratorKindEnum kind; JSMapState *s; JSMapIteratorData *it; JSValue enum_obj; kind = magic >> 2; magic &= 3; s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); if (!s) return JS_EXCEPTION; enum_obj = JS_NewObjectClass(ctx, JS_CLASS_MAP_ITERATOR + magic); if (JS_IsException(enum_obj)) goto fail; it = js_malloc(ctx, sizeof(*it)); if (!it) { JS_FreeValue(ctx, enum_obj); goto fail; } it->obj = JS_DupValue(ctx, this_val); it->kind = kind; it->cur_record = NULL; JS_SetOpaque(enum_obj, it); return enum_obj; fail: return JS_EXCEPTION; } static JSValue js_map_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, BOOL *pdone, int magic) { JSMapIteratorData *it; JSMapState *s; JSMapRecord *mr; struct list_head *el; it = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP_ITERATOR + magic); if (!it) { *pdone = FALSE; return JS_EXCEPTION; } if (JS_IsUndefined(it->obj)) goto done; s = JS_GetOpaque(it->obj, JS_CLASS_MAP + magic); assert(s != NULL); if (!it->cur_record) { el = s->records.next; } else { mr = it->cur_record; el = mr->link.next; map_decref_record(ctx->rt, mr); /* the record can be freed here */ } for(;;) { if (el == &s->records) { /* no more record */ it->cur_record = NULL; JS_FreeValue(ctx, it->obj); it->obj = JS_UNDEFINED; done: /* end of enumeration */ *pdone = TRUE; return JS_UNDEFINED; } mr = list_entry(el, JSMapRecord, link); if (!mr->empty) break; /* get the next record */ el = mr->link.next; } /* lock the record so that it won't be freed */ mr->ref_count++; it->cur_record = mr; *pdone = FALSE; if (it->kind == JS_ITERATOR_KIND_KEY) { return JS_DupValue(ctx, mr->key); } else { JSValueConst args[2]; args[0] = mr->key; if (magic) args[1] = mr->key; else args[1] = mr->value; if (it->kind == JS_ITERATOR_KIND_VALUE) { return JS_DupValue(ctx, args[1]); } else { return js_create_array(ctx, 2, args); } } } static const JSCFunctionListEntry js_map_funcs[] = { JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), }; static const JSCFunctionListEntry js_map_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0 ), JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0 ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0 ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0 ), JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0 ), JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, 0), JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, 0 ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_VALUE << 2) | 0 ), JS_CFUNC_MAGIC_DEF("keys", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | 0 ), JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | 0 ), JS_ALIAS_DEF("[Symbol.iterator]", "entries" ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_map_iterator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, 0 ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map Iterator", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_set_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET ), JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET ), JS_ALIAS_DEF("keys", "values" ), JS_ALIAS_DEF("[Symbol.iterator]", "values" ), JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | MAGIC_SET ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_set_iterator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, MAGIC_SET ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set Iterator", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_weak_map_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakMap", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_weak_set_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET | MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET | MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET | MAGIC_WEAK ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakSet", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry * const js_map_proto_funcs_ptr[6] = { js_map_proto_funcs, js_set_proto_funcs, js_weak_map_proto_funcs, js_weak_set_proto_funcs, js_map_iterator_proto_funcs, js_set_iterator_proto_funcs, }; static const uint8_t js_map_proto_funcs_count[6] = { countof(js_map_proto_funcs), countof(js_set_proto_funcs), countof(js_weak_map_proto_funcs), countof(js_weak_set_proto_funcs), countof(js_map_iterator_proto_funcs), countof(js_set_iterator_proto_funcs), }; void JS_AddIntrinsicMapSet(JSContext *ctx) { int i; JSValue obj1; char buf[ATOM_GET_STR_BUF_SIZE]; for(i = 0; i < 4; i++) { const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Map + i); ctx->class_proto[JS_CLASS_MAP + i] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP + i], js_map_proto_funcs_ptr[i], js_map_proto_funcs_count[i]); obj1 = JS_NewCFunctionMagic(ctx, js_map_constructor, name, 0, JS_CFUNC_constructor_magic, i); if (i < 2) { JS_SetPropertyFunctionList(ctx, obj1, js_map_funcs, countof(js_map_funcs)); } JS_NewGlobalCConstructor2(ctx, obj1, name, ctx->class_proto[JS_CLASS_MAP + i]); } for(i = 0; i < 2; i++) { ctx->class_proto[JS_CLASS_MAP_ITERATOR + i] = JS_NewObjectProto(ctx, ctx->iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP_ITERATOR + i], js_map_proto_funcs_ptr[i + 4], js_map_proto_funcs_count[i + 4]); } } /* Generator */ static const JSCFunctionListEntry js_generator_function_proto_funcs[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "GeneratorFunction", JS_PROP_CONFIGURABLE), }; static const JSCFunctionListEntry js_generator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 1, js_generator_next, GEN_MAGIC_NEXT ), JS_ITERATOR_NEXT_DEF("return", 1, js_generator_next, GEN_MAGIC_RETURN ), JS_ITERATOR_NEXT_DEF("throw", 1, js_generator_next, GEN_MAGIC_THROW ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Generator", JS_PROP_CONFIGURABLE), }; /* Promise */ typedef enum JSPromiseStateEnum { JS_PROMISE_PENDING, JS_PROMISE_FULFILLED, JS_PROMISE_REJECTED, } JSPromiseStateEnum; typedef struct JSPromiseData { JSPromiseStateEnum promise_state; /* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */ struct list_head promise_reactions[2]; BOOL is_handled; /* Note: only useful to debug */ JSValue promise_result; } JSPromiseData; typedef struct JSPromiseFunctionDataResolved { int ref_count; BOOL already_resolved; } JSPromiseFunctionDataResolved; typedef struct JSPromiseFunctionData { JSValue promise; JSPromiseFunctionDataResolved *presolved; } JSPromiseFunctionData; typedef struct JSPromiseReactionData { struct list_head link; /* not used in promise_reaction_job */ JSValue resolving_funcs[2]; JSValue handler; } JSPromiseReactionData; static int js_create_resolving_functions(JSContext *ctx, JSValue *args, JSValueConst promise); static void promise_reaction_data_free(JSRuntime *rt, JSPromiseReactionData *rd) { JS_FreeValueRT(rt, rd->resolving_funcs[0]); JS_FreeValueRT(rt, rd->resolving_funcs[1]); JS_FreeValueRT(rt, rd->handler); js_free_rt(rt, rd); } static JSValue promise_reaction_job(JSContext *ctx, int argc, JSValueConst *argv) { JSValueConst handler, arg, func; JSValue res, res2; BOOL is_reject; assert(argc == 5); handler = argv[2]; is_reject = JS_ToBool(ctx, argv[3]); arg = argv[4]; #ifdef DUMP_PROMISE printf("promise_reaction_job: is_reject=%d\n", is_reject); #endif if (JS_IsUndefined(handler)) { if (is_reject) { res = JS_Throw(ctx, JS_DupValue(ctx, arg)); } else { res = JS_DupValue(ctx, arg); } } else { res = JS_Call(ctx, handler, JS_UNDEFINED, 1, &arg); } is_reject = JS_IsException(res); if (is_reject) res = JS_GetException(ctx); func = argv[is_reject]; /* as an extension, we support undefined as value to avoid creating a dummy promise in the 'await' implementation of async functions */ if (!JS_IsUndefined(func)) { res2 = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&res); } else { res2 = JS_UNDEFINED; } JS_FreeValue(ctx, res); return res2; } void JS_SetHostPromiseRejectionTracker(JSRuntime *rt, JSHostPromiseRejectionTracker *cb, void *opaque) { rt->host_promise_rejection_tracker = cb; rt->host_promise_rejection_tracker_opaque = opaque; } static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise, JSValueConst value, BOOL is_reject) { JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); struct list_head *el, *el1; JSPromiseReactionData *rd; JSValueConst args[5]; if (!s || s->promise_state != JS_PROMISE_PENDING) return; /* should never happen */ set_value(ctx, &s->promise_result, JS_DupValue(ctx, value)); s->promise_state = JS_PROMISE_FULFILLED + is_reject; #ifdef DUMP_PROMISE printf("fulfill_or_reject_promise: is_reject=%d\n", is_reject); #endif if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) { JSRuntime *rt = ctx->rt; if (rt->host_promise_rejection_tracker) { rt->host_promise_rejection_tracker(ctx, promise, value, FALSE, rt->host_promise_rejection_tracker_opaque); } } list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) { rd = list_entry(el, JSPromiseReactionData, link); args[0] = rd->resolving_funcs[0]; args[1] = rd->resolving_funcs[1]; args[2] = rd->handler; args[3] = JS_NewBool(ctx, is_reject); args[4] = value; JS_EnqueueJob(ctx, promise_reaction_job, 5, args); list_del(&rd->link); promise_reaction_data_free(ctx->rt, rd); } list_for_each_safe(el, el1, &s->promise_reactions[1 - is_reject]) { rd = list_entry(el, JSPromiseReactionData, link); list_del(&rd->link); promise_reaction_data_free(ctx->rt, rd); } } static void reject_promise(JSContext *ctx, JSValueConst promise, JSValueConst value) { fulfill_or_reject_promise(ctx, promise, value, TRUE); } static JSValue js_promise_resolve_thenable_job(JSContext *ctx, int argc, JSValueConst *argv) { JSValueConst promise, thenable, then; JSValue args[2], res; #ifdef DUMP_PROMISE printf("js_promise_resolve_thenable_job\n"); #endif assert(argc == 3); promise = argv[0]; thenable = argv[1]; then = argv[2]; if (js_create_resolving_functions(ctx, args, promise) < 0) return JS_EXCEPTION; res = JS_Call(ctx, then, thenable, 2, (JSValueConst *)args); if (JS_IsException(res)) { JSValue error = JS_GetException(ctx); res = JS_Call(ctx, args[1], JS_UNDEFINED, 1, (JSValueConst *)&error); JS_FreeValue(ctx, error); } JS_FreeValue(ctx, args[0]); JS_FreeValue(ctx, args[1]); return res; } static void js_promise_resolve_function_free_resolved(JSRuntime *rt, JSPromiseFunctionDataResolved *sr) { if (--sr->ref_count == 0) { js_free_rt(rt, sr); } } static int js_create_resolving_functions(JSContext *ctx, JSValue *resolving_funcs, JSValueConst promise) { JSValue obj; JSPromiseFunctionData *s; JSPromiseFunctionDataResolved *sr; int i, ret; sr = js_malloc(ctx, sizeof(*sr)); if (!sr) return -1; sr->ref_count = 1; sr->already_resolved = FALSE; /* must be shared between the two functions */ ret = 0; for(i = 0; i < 2; i++) { obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, JS_CLASS_PROMISE_RESOLVE_FUNCTION + i); if (JS_IsException(obj)) goto fail; s = js_malloc(ctx, sizeof(*s)); if (!s) { JS_FreeValue(ctx, obj); fail: if (i != 0) JS_FreeValue(ctx, resolving_funcs[0]); ret = -1; break; } sr->ref_count++; s->presolved = sr; s->promise = JS_DupValue(ctx, promise); JS_SetOpaque(obj, s); js_function_set_properties(ctx, obj, JS_ATOM_empty_string, 1); resolving_funcs[i] = obj; } js_promise_resolve_function_free_resolved(ctx->rt, sr); return ret; } static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val) { JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data; if (s) { js_promise_resolve_function_free_resolved(rt, s->presolved); JS_FreeValueRT(rt, s->promise); js_free_rt(rt, s); } } static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data; if (s) { JS_MarkValue(rt, s->promise, mark_func); } } static JSValue js_promise_resolve_function_call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst *argv, int flags) { JSObject *p = JS_VALUE_GET_OBJ(func_obj); JSPromiseFunctionData *s; JSValueConst resolution, args[3]; JSValue then; BOOL is_reject; s = p->u.promise_function_data; if (!s || s->presolved->already_resolved) return JS_UNDEFINED; s->presolved->already_resolved = TRUE; is_reject = p->class_id - JS_CLASS_PROMISE_RESOLVE_FUNCTION; if (argc > 0) resolution = argv[0]; else resolution = JS_UNDEFINED; #ifdef DUMP_PROMISE printf("js_promise_resolving_function_call: is_reject=%d resolution=", is_reject); JS_DumpValue(ctx, resolution); printf("\n"); #endif if (is_reject || !JS_IsObject(resolution)) { goto done; } else if (js_same_value(ctx, resolution, s->promise)) { JS_ThrowTypeError(ctx, "promise self resolution"); goto fail_reject; } then = JS_GetProperty(ctx, resolution, JS_ATOM_then); if (JS_IsException(then)) { JSValue error; fail_reject: error = JS_GetException(ctx); reject_promise(ctx, s->promise, error); JS_FreeValue(ctx, error); } else if (!JS_IsFunction(ctx, then)) { JS_FreeValue(ctx, then); done: fulfill_or_reject_promise(ctx, s->promise, resolution, is_reject); } else { args[0] = s->promise; args[1] = resolution; args[2] = then; JS_EnqueueJob(ctx, js_promise_resolve_thenable_job, 3, args); JS_FreeValue(ctx, then); } return JS_UNDEFINED; } static void js_promise_finalizer(JSRuntime *rt, JSValue val) { JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE); struct list_head *el, *el1; int i; if (!s) return; for(i = 0; i < 2; i++) { list_for_each_safe(el, el1, &s->promise_reactions[i]) { JSPromiseReactionData *rd = list_entry(el, JSPromiseReactionData, link); promise_reaction_data_free(rt, rd); } } JS_FreeValueRT(rt, s->promise_result); js_free_rt(rt, s); } static void js_promise_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE); struct list_head *el; int i; if (!s) return; for(i = 0; i < 2; i++) { list_for_each(el, &s->promise_reactions[i]) { JSPromiseReactionData *rd = list_entry(el, JSPromiseReactionData, link); JS_MarkValue(rt, rd->resolving_funcs[0], mark_func); JS_MarkValue(rt, rd->resolving_funcs[1], mark_func); JS_MarkValue(rt, rd->handler, mark_func); } } JS_MarkValue(rt, s->promise_result, mark_func); } static JSValue js_promise_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValueConst executor; JSValue obj; JSPromiseData *s; JSValue args[2], ret; int i; executor = argv[0]; if (check_function(ctx, executor)) return JS_EXCEPTION; obj = js_create_from_ctor(ctx, new_target, JS_CLASS_PROMISE); if (JS_IsException(obj)) return JS_EXCEPTION; s = js_mallocz(ctx, sizeof(*s)); if (!s) goto fail; s->promise_state = JS_PROMISE_PENDING; s->is_handled = FALSE; for(i = 0; i < 2; i++) init_list_head(&s->promise_reactions[i]); s->promise_result = JS_UNDEFINED; JS_SetOpaque(obj, s); if (js_create_resolving_functions(ctx, args, obj)) goto fail; ret = JS_Call(ctx, executor, JS_UNDEFINED, 2, (JSValueConst *)args); if (JS_IsException(ret)) { JSValue ret2, error; error = JS_GetException(ctx); ret2 = JS_Call(ctx, args[1], JS_UNDEFINED, 1, (JSValueConst *)&error); JS_FreeValue(ctx, error); if (JS_IsException(ret2)) goto fail1; JS_FreeValue(ctx, ret2); } JS_FreeValue(ctx, ret); JS_FreeValue(ctx, args[0]); JS_FreeValue(ctx, args[1]); return obj; fail1: JS_FreeValue(ctx, args[0]); JS_FreeValue(ctx, args[1]); fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_promise_executor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { int i; for(i = 0; i < 2; i++) { if (!JS_IsUndefined(func_data[i])) return JS_ThrowTypeError(ctx, "resolving function already set"); func_data[i] = JS_DupValue(ctx, argv[i]); } return JS_UNDEFINED; } static JSValue js_promise_executor_new(JSContext *ctx) { JSValueConst func_data[2]; func_data[0] = JS_UNDEFINED; func_data[1] = JS_UNDEFINED; return JS_NewCFunctionData(ctx, js_promise_executor, 2, 0, 2, func_data); } static JSValue js_new_promise_capability(JSContext *ctx, JSValue *resolving_funcs, JSValueConst ctor) { JSValue executor, result_promise; JSCFunctionDataRecord *s; int i; executor = js_promise_executor_new(ctx); if (JS_IsException(executor)) return executor; if (JS_IsUndefined(ctor)) { result_promise = js_promise_constructor(ctx, ctor, 1, (JSValueConst *)&executor); } else { result_promise = JS_CallConstructor(ctx, ctor, 1, (JSValueConst *)&executor); } if (JS_IsException(result_promise)) goto fail; s = JS_GetOpaque(executor, JS_CLASS_C_FUNCTION_DATA); for(i = 0; i < 2; i++) { if (check_function(ctx, s->data[i])) goto fail; } for(i = 0; i < 2; i++) resolving_funcs[i] = JS_DupValue(ctx, s->data[i]); JS_FreeValue(ctx, executor); return result_promise; fail: JS_FreeValue(ctx, executor); JS_FreeValue(ctx, result_promise); return JS_EXCEPTION; } JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs) { return js_new_promise_capability(ctx, resolving_funcs, JS_UNDEFINED); } static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue result_promise, resolving_funcs[2], ret; BOOL is_reject = magic; if (!JS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx); if (!is_reject && JS_GetOpaque(argv[0], JS_CLASS_PROMISE)) { JSValue ctor; BOOL is_same; ctor = JS_GetProperty(ctx, argv[0], JS_ATOM_constructor); if (JS_IsException(ctor)) return ctor; is_same = js_same_value(ctx, ctor, this_val); JS_FreeValue(ctx, ctor); if (is_same) return JS_DupValue(ctx, argv[0]); } result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); if (JS_IsException(result_promise)) return result_promise; ret = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, 1, argv); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); if (JS_IsException(ret)) { JS_FreeValue(ctx, result_promise); return ret; } JS_FreeValue(ctx, ret); return result_promise; } #if 0 static JSValue js_promise___newPromiseCapability(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue result_promise, resolving_funcs[2], obj; JSValueConst ctor; ctor = argv[0]; if (!JS_IsObject(ctor)) return JS_ThrowTypeErrorNotAnObject(ctx); result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor); if (JS_IsException(result_promise)) return result_promise; obj = JS_NewObject(ctx); if (JS_IsException(obj)) { JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); JS_FreeValue(ctx, result_promise); return JS_EXCEPTION; } JS_DefinePropertyValue(ctx, obj, JS_ATOM_promise, result_promise, JS_PROP_C_W_E); JS_DefinePropertyValue(ctx, obj, JS_ATOM_resolve, resolving_funcs[0], JS_PROP_C_W_E); JS_DefinePropertyValue(ctx, obj, JS_ATOM_reject, resolving_funcs[1], JS_PROP_C_W_E); return obj; } #endif static __exception int remainingElementsCount_add(JSContext *ctx, JSValueConst resolve_element_env, int addend) { JSValue val; int remainingElementsCount; val = JS_GetPropertyUint32(ctx, resolve_element_env, 0); if (JS_IsException(val)) return -1; if (JS_ToInt32Free(ctx, &remainingElementsCount, val)) return -1; remainingElementsCount += addend; if (JS_SetPropertyUint32(ctx, resolve_element_env, 0, JS_NewInt32(ctx, remainingElementsCount)) < 0) return -1; return (remainingElementsCount == 0); } static JSValue js_promise_all_resolve_element(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { int is_allSettled = magic & 1; int is_reject = magic & 2; BOOL alreadyCalled = JS_ToBool(ctx, func_data[0]); JSValueConst values = func_data[2]; JSValueConst resolve = func_data[3]; JSValueConst resolve_element_env = func_data[4]; JSValue ret, obj; int is_zero, index; if (JS_ToInt32(ctx, &index, func_data[1])) return JS_EXCEPTION; if (alreadyCalled) return JS_UNDEFINED; func_data[0] = JS_NewBool(ctx, TRUE); if (is_allSettled) { JSValue str; obj = JS_NewObject(ctx); if (JS_IsException(obj)) return JS_EXCEPTION; str = JS_NewString(ctx, is_reject ? "rejected" : "fulfilled"); if (JS_IsException(str)) goto fail1; if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_status, str, JS_PROP_C_W_E) < 0) goto fail1; if (JS_DefinePropertyValue(ctx, obj, is_reject ? JS_ATOM_reason : JS_ATOM_value, JS_DupValue(ctx, argv[0]), JS_PROP_C_W_E) < 0) { fail1: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } } else { obj = JS_DupValue(ctx, argv[0]); } if (JS_DefinePropertyValueUint32(ctx, values, index, obj, JS_PROP_C_W_E) < 0) return JS_EXCEPTION; is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1); if (is_zero < 0) return JS_EXCEPTION; if (is_zero) { ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, (JSValueConst *)&values); if (JS_IsException(ret)) return ret; JS_FreeValue(ctx, ret); } return JS_UNDEFINED; } /* magic = 0: Promise.all 1: Promise.allSettled */ static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue result_promise, resolving_funcs[2], iter, item, next_promise, ret; JSValue next_method = JS_UNDEFINED, values = JS_UNDEFINED; JSValue resolve_element_env = JS_UNDEFINED, resolve_element, reject_element; JSValue promise_resolve = JS_UNDEFINED; JSValueConst then_args[2], resolve_element_data[5]; BOOL done, is_allSettled = magic; int index, is_zero; if (!JS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx); result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); if (JS_IsException(result_promise)) return result_promise; iter = JS_GetIterator(ctx, argv[0], FALSE); if (JS_IsException(iter)) { JSValue error; fail_reject: error = JS_GetException(ctx); ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&error); JS_FreeValue(ctx, error); if (JS_IsException(ret)) goto fail; JS_FreeValue(ctx, ret); } else { next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); if (JS_IsException(next_method)) goto fail_reject; values = JS_NewArray(ctx); if (JS_IsException(values)) goto fail_reject; resolve_element_env = JS_NewArray(ctx); if (JS_IsException(resolve_element_env)) goto fail_reject; /* remainingElementsCount field */ if (JS_DefinePropertyValueUint32(ctx, resolve_element_env, 0, JS_NewInt32(ctx, 1), JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) goto fail_reject; promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); if (JS_IsException(promise_resolve) || check_function(ctx, promise_resolve)) goto fail_reject1; index = 0; for(;;) { /* XXX: conformance: should close the iterator if error on 'done' access, but not on 'value' access */ item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(item)) goto fail_reject; if (done) break; next_promise = JS_Call(ctx, promise_resolve, this_val, 1, (JSValueConst *)&item); JS_FreeValue(ctx, item); if (JS_IsException(next_promise)) { fail_reject1: JS_IteratorClose(ctx, iter, TRUE); goto fail_reject; } resolve_element_data[0] = JS_NewBool(ctx, FALSE); resolve_element_data[1] = (JSValueConst)JS_NewInt32(ctx, index); resolve_element_data[2] = values; resolve_element_data[3] = resolving_funcs[0]; resolve_element_data[4] = resolve_element_env; resolve_element = JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1, is_allSettled, 5, resolve_element_data); if (JS_IsException(resolve_element)) { JS_FreeValue(ctx, next_promise); goto fail_reject1; } if (is_allSettled) { reject_element = JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1, is_allSettled | 2, 5, resolve_element_data); if (JS_IsException(reject_element)) { JS_FreeValue(ctx, next_promise); goto fail_reject1; } } else { reject_element = JS_DupValue(ctx, resolving_funcs[1]); } if (remainingElementsCount_add(ctx, resolve_element_env, 1) < 0) { JS_FreeValue(ctx, next_promise); JS_FreeValue(ctx, resolve_element); JS_FreeValue(ctx, reject_element); goto fail_reject1; } then_args[0] = resolve_element; then_args[1] = reject_element; ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, then_args); JS_FreeValue(ctx, resolve_element); JS_FreeValue(ctx, reject_element); if (check_exception_free(ctx, ret)) goto fail_reject1; index++; } is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1); if (is_zero < 0) goto fail_reject; if (is_zero) { ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&values); if (check_exception_free(ctx, ret)) goto fail_reject; } } done: JS_FreeValue(ctx, promise_resolve); JS_FreeValue(ctx, resolve_element_env); JS_FreeValue(ctx, values); JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); return result_promise; fail: JS_FreeValue(ctx, result_promise); result_promise = JS_EXCEPTION; goto done; } static JSValue js_promise_race(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue result_promise, resolving_funcs[2], iter, item, next_promise, ret; JSValue next_method = JS_UNDEFINED; JSValue promise_resolve = JS_UNDEFINED; BOOL done; if (!JS_IsObject(this_val)) return JS_ThrowTypeErrorNotAnObject(ctx); result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); if (JS_IsException(result_promise)) return result_promise; iter = JS_GetIterator(ctx, argv[0], FALSE); if (JS_IsException(iter)) { JSValue error; fail_reject: error = JS_GetException(ctx); ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&error); JS_FreeValue(ctx, error); if (JS_IsException(ret)) goto fail; JS_FreeValue(ctx, ret); } else { next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); if (JS_IsException(next_method)) goto fail_reject; promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); if (JS_IsException(promise_resolve) || check_function(ctx, promise_resolve)) goto fail_reject1; for(;;) { /* XXX: conformance: should close the iterator if error on 'done' access, but not on 'value' access */ item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(item)) goto fail_reject; if (done) break; next_promise = JS_Call(ctx, promise_resolve, this_val, 1, (JSValueConst *)&item); JS_FreeValue(ctx, item); if (JS_IsException(next_promise)) { fail_reject1: JS_IteratorClose(ctx, iter, TRUE); goto fail_reject; } ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, (JSValueConst *)resolving_funcs); if (check_exception_free(ctx, ret)) goto fail_reject1; } } done: JS_FreeValue(ctx, promise_resolve); JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); return result_promise; fail: //JS_FreeValue(ctx, next_method); // why not??? JS_FreeValue(ctx, result_promise); result_promise = JS_EXCEPTION; goto done; } static __exception int perform_promise_then(JSContext *ctx, JSValueConst promise, JSValueConst *resolve_reject, JSValueConst *cap_resolving_funcs) { JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); JSPromiseReactionData *rd_array[2], *rd; int i, j; rd_array[0] = NULL; rd_array[1] = NULL; for(i = 0; i < 2; i++) { JSValueConst handler; rd = js_mallocz(ctx, sizeof(*rd)); if (!rd) { if (i == 1) promise_reaction_data_free(ctx->rt, rd_array[0]); return -1; } for(j = 0; j < 2; j++) rd->resolving_funcs[j] = JS_DupValue(ctx, cap_resolving_funcs[j]); handler = resolve_reject[i]; if (!JS_IsFunction(ctx, handler)) handler = JS_UNDEFINED; rd->handler = JS_DupValue(ctx, handler); rd_array[i] = rd; } if (s->promise_state == JS_PROMISE_PENDING) { for(i = 0; i < 2; i++) list_add_tail(&rd_array[i]->link, &s->promise_reactions[i]); } else { JSValueConst args[5]; if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) { JSRuntime *rt = ctx->rt; if (rt->host_promise_rejection_tracker) { rt->host_promise_rejection_tracker(ctx, promise, s->promise_result, TRUE, rt->host_promise_rejection_tracker_opaque); } } i = s->promise_state - JS_PROMISE_FULFILLED; rd = rd_array[i]; args[0] = rd->resolving_funcs[0]; args[1] = rd->resolving_funcs[1]; args[2] = rd->handler; args[3] = JS_NewBool(ctx, i); args[4] = s->promise_result; JS_EnqueueJob(ctx, promise_reaction_job, 5, args); for(i = 0; i < 2; i++) promise_reaction_data_free(ctx->rt, rd_array[i]); } s->is_handled = TRUE; return 0; } static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue ctor, result_promise, resolving_funcs[2]; JSPromiseData *s; int i, ret; s = JS_GetOpaque2(ctx, this_val, JS_CLASS_PROMISE); if (!s) return JS_EXCEPTION; ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED); if (JS_IsException(ctor)) return ctor; result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor); JS_FreeValue(ctx, ctor); if (JS_IsException(result_promise)) return result_promise; ret = perform_promise_then(ctx, this_val, argv, (JSValueConst *)resolving_funcs); for(i = 0; i < 2; i++) JS_FreeValue(ctx, resolving_funcs[i]); if (ret) { JS_FreeValue(ctx, result_promise); return JS_EXCEPTION; } return result_promise; } static JSValue js_promise_catch(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst args[2]; args[0] = JS_UNDEFINED; args[1] = argv[0]; return JS_Invoke(ctx, this_val, JS_ATOM_then, 2, args); } static JSValue js_promise_finally_value_thunk(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { return JS_DupValue(ctx, func_data[0]); } static JSValue js_promise_finally_thrower(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { return JS_Throw(ctx, JS_DupValue(ctx, func_data[0])); } static JSValue js_promise_then_finally_func(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { JSValueConst ctor = func_data[0]; JSValueConst onFinally = func_data[1]; JSValue res, promise, resolving_funcs[2], ret, then_func; res = JS_Call(ctx, onFinally, JS_UNDEFINED, 0, NULL); if (JS_IsException(res)) return res; promise = js_new_promise_capability(ctx, resolving_funcs, ctor); if (JS_IsException(promise)) { JS_FreeValue(ctx, res); return JS_EXCEPTION; } ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst*)&res); JS_FreeValue(ctx, res); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); if (JS_IsException(ret)) { JS_FreeValue(ctx, promise); return ret; } JS_FreeValue(ctx, ret); if (magic == 0) { then_func = JS_NewCFunctionData(ctx, js_promise_finally_value_thunk, 1, 0, 1, argv); } else { then_func = JS_NewCFunctionData(ctx, js_promise_finally_thrower, 1, 0, 1, argv); } if (JS_IsException(then_func)) { JS_FreeValue(ctx, promise); return then_func; } ret = JS_InvokeFree(ctx, promise, JS_ATOM_then, 1, (JSValueConst *)&then_func); JS_FreeValue(ctx, then_func); return ret; } static JSValue js_promise_finally(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst onFinally = argv[0]; JSValue ctor, ret; JSValue then_funcs[2]; JSValueConst func_data[2]; int i; ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED); if (JS_IsException(ctor)) return ctor; if (!JS_IsFunction(ctx, onFinally)) { then_funcs[0] = JS_DupValue(ctx, onFinally); then_funcs[1] = JS_DupValue(ctx, onFinally); } else { func_data[0] = ctor; func_data[1] = onFinally; for(i = 0; i < 2; i++) { then_funcs[i] = JS_NewCFunctionData(ctx, js_promise_then_finally_func, 1, i, 2, func_data); if (JS_IsException(then_funcs[i])) { if (i == 1) JS_FreeValue(ctx, then_funcs[0]); JS_FreeValue(ctx, ctor); return JS_EXCEPTION; } } } JS_FreeValue(ctx, ctor); ret = JS_Invoke(ctx, this_val, JS_ATOM_then, 2, (JSValueConst *)then_funcs); JS_FreeValue(ctx, then_funcs[0]); JS_FreeValue(ctx, then_funcs[1]); return ret; } static const JSCFunctionListEntry js_promise_funcs[] = { JS_CFUNC_MAGIC_DEF("resolve", 1, js_promise_resolve, 0 ), JS_CFUNC_MAGIC_DEF("reject", 1, js_promise_resolve, 1 ), JS_CFUNC_MAGIC_DEF("all", 1, js_promise_all, 0 ), JS_CFUNC_MAGIC_DEF("allSettled", 1, js_promise_all, 1 ), JS_CFUNC_DEF("race", 1, js_promise_race ), //JS_CFUNC_DEF("__newPromiseCapability", 1, js_promise___newPromiseCapability ), JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL), }; static const JSCFunctionListEntry js_promise_proto_funcs[] = { JS_CFUNC_DEF("then", 2, js_promise_then ), JS_CFUNC_DEF("catch", 1, js_promise_catch ), JS_CFUNC_DEF("finally", 1, js_promise_finally ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Promise", JS_PROP_CONFIGURABLE ), }; /* AsyncFunction */ static const JSCFunctionListEntry js_async_function_proto_funcs[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncFunction", JS_PROP_CONFIGURABLE ), }; static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { return js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]), JS_ToBool(ctx, func_data[0])); } static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx, BOOL done) { JSValueConst func_data[1]; func_data[0] = (JSValueConst)JS_NewBool(ctx, done); return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap, 1, 0, 1, func_data); } /* AsyncIteratorPrototype */ static const JSCFunctionListEntry js_async_iterator_proto_funcs[] = { JS_CFUNC_DEF("[Symbol.asyncIterator]", 0, js_iterator_proto_iterator ), }; /* AsyncFromSyncIteratorPrototype */ typedef struct JSAsyncFromSyncIteratorData { JSValue sync_iter; JSValue next_method; } JSAsyncFromSyncIteratorData; static void js_async_from_sync_iterator_finalizer(JSRuntime *rt, JSValue val) { JSAsyncFromSyncIteratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); if (s) { JS_FreeValueRT(rt, s->sync_iter); JS_FreeValueRT(rt, s->next_method); js_free_rt(rt, s); } } static void js_async_from_sync_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSAsyncFromSyncIteratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); if (s) { JS_MarkValue(rt, s->sync_iter, mark_func); JS_MarkValue(rt, s->next_method, mark_func); } } static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, JSValueConst sync_iter) { JSValue async_iter, next_method; JSAsyncFromSyncIteratorData *s; next_method = JS_GetProperty(ctx, sync_iter, JS_ATOM_next); if (JS_IsException(next_method)) return JS_EXCEPTION; async_iter = JS_NewObjectClass(ctx, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); if (JS_IsException(async_iter)) { JS_FreeValue(ctx, next_method); return async_iter; } s = js_mallocz(ctx, sizeof(*s)); if (!s) { JS_FreeValue(ctx, async_iter); JS_FreeValue(ctx, next_method); return JS_EXCEPTION; } s->sync_iter = JS_DupValue(ctx, sync_iter); s->next_method = next_method; JS_SetOpaque(async_iter, s); return async_iter; } static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSValue promise, resolving_funcs[2], value, err, method; JSAsyncFromSyncIteratorData *s; int done; int is_reject; promise = JS_NewPromiseCapability(ctx, resolving_funcs); if (JS_IsException(promise)) return JS_EXCEPTION; s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); if (!s) { JS_ThrowTypeError(ctx, "not an Async-from-Sync Iterator"); goto reject; } if (magic == GEN_MAGIC_NEXT) { method = JS_DupValue(ctx, s->next_method); } else { method = JS_GetProperty(ctx, s->sync_iter, magic == GEN_MAGIC_RETURN ? JS_ATOM_return : JS_ATOM_throw); if (JS_IsException(method)) goto reject; if (JS_IsUndefined(method) || JS_IsNull(method)) { if (magic == GEN_MAGIC_RETURN) { err = js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]), TRUE); is_reject = 0; } else { err = JS_DupValue(ctx, argv[0]); is_reject = 1; } goto done_resolve; } } value = JS_IteratorNext2(ctx, s->sync_iter, method, 1, argv, &done); JS_FreeValue(ctx, method); if (JS_IsException(value)) goto reject; if (done == 2) { JSValue obj = value; value = JS_IteratorGetCompleteValue(ctx, obj, &done); JS_FreeValue(ctx, obj); if (JS_IsException(value)) goto reject; } if (JS_IsException(value)) { JSValue res2; reject: err = JS_GetException(ctx); is_reject = 1; done_resolve: res2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, 1, (JSValueConst *)&err); JS_FreeValue(ctx, err); JS_FreeValue(ctx, res2); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); return promise; } { JSValue value_wrapper_promise, resolve_reject[2]; int res; value_wrapper_promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&value, 0); if (JS_IsException(value_wrapper_promise)) { JS_FreeValue(ctx, value); goto reject; } resolve_reject[0] = js_async_from_sync_iterator_unwrap_func_create(ctx, done); if (JS_IsException(resolve_reject[0])) { JS_FreeValue(ctx, value_wrapper_promise); goto fail; } JS_FreeValue(ctx, value); resolve_reject[1] = JS_UNDEFINED; res = perform_promise_then(ctx, value_wrapper_promise, (JSValueConst *)resolve_reject, (JSValueConst *)resolving_funcs); JS_FreeValue(ctx, resolve_reject[0]); JS_FreeValue(ctx, value_wrapper_promise); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); if (res) { JS_FreeValue(ctx, promise); return JS_EXCEPTION; } } return promise; fail: JS_FreeValue(ctx, value); JS_FreeValue(ctx, resolving_funcs[0]); JS_FreeValue(ctx, resolving_funcs[1]); JS_FreeValue(ctx, promise); return JS_EXCEPTION; } static const JSCFunctionListEntry js_async_from_sync_iterator_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("next", 1, js_async_from_sync_iterator_next, GEN_MAGIC_NEXT ), JS_CFUNC_MAGIC_DEF("return", 1, js_async_from_sync_iterator_next, GEN_MAGIC_RETURN ), JS_CFUNC_MAGIC_DEF("throw", 1, js_async_from_sync_iterator_next, GEN_MAGIC_THROW ), }; /* AsyncGeneratorFunction */ static const JSCFunctionListEntry js_async_generator_function_proto_funcs[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGeneratorFunction", JS_PROP_CONFIGURABLE ), }; /* AsyncGenerator prototype */ static const JSCFunctionListEntry js_async_generator_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("next", 1, js_async_generator_next, GEN_MAGIC_NEXT ), JS_CFUNC_MAGIC_DEF("return", 1, js_async_generator_next, GEN_MAGIC_RETURN ), JS_CFUNC_MAGIC_DEF("throw", 1, js_async_generator_next, GEN_MAGIC_THROW ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGenerator", JS_PROP_CONFIGURABLE ), }; static JSClassShortDef const js_async_class_def[] = { { JS_ATOM_Promise, js_promise_finalizer, js_promise_mark }, /* JS_CLASS_PROMISE */ { JS_ATOM_PromiseResolveFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_RESOLVE_FUNCTION */ { JS_ATOM_PromiseRejectFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_REJECT_FUNCTION */ { JS_ATOM_AsyncFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_FUNCTION */ { JS_ATOM_AsyncFunctionResolve, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_RESOLVE */ { JS_ATOM_AsyncFunctionReject, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_REJECT */ { JS_ATOM_empty_string, js_async_from_sync_iterator_finalizer, js_async_from_sync_iterator_mark }, /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ { JS_ATOM_AsyncGeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_GENERATOR_FUNCTION */ { JS_ATOM_AsyncGenerator, js_async_generator_finalizer, js_async_generator_mark }, /* JS_CLASS_ASYNC_GENERATOR */ }; void JS_AddIntrinsicPromise(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; if (!JS_IsRegisteredClass(rt, JS_CLASS_PROMISE)) { init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE, countof(js_async_class_def)); rt->class_array[JS_CLASS_PROMISE_RESOLVE_FUNCTION].call = js_promise_resolve_function_call; rt->class_array[JS_CLASS_PROMISE_REJECT_FUNCTION].call = js_promise_resolve_function_call; rt->class_array[JS_CLASS_ASYNC_FUNCTION].call = js_async_function_call; rt->class_array[JS_CLASS_ASYNC_FUNCTION_RESOLVE].call = js_async_function_resolve_call; rt->class_array[JS_CLASS_ASYNC_FUNCTION_REJECT].call = js_async_function_resolve_call; rt->class_array[JS_CLASS_ASYNC_GENERATOR_FUNCTION].call = js_async_generator_function_call; } /* Promise */ ctx->class_proto[JS_CLASS_PROMISE] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_PROMISE], js_promise_proto_funcs, countof(js_promise_proto_funcs)); obj1 = JS_NewCFunction2(ctx, js_promise_constructor, "Promise", 1, JS_CFUNC_constructor, 0); ctx->promise_ctor = JS_DupValue(ctx, obj1); JS_SetPropertyFunctionList(ctx, obj1, js_promise_funcs, countof(js_promise_funcs)); JS_NewGlobalCConstructor2(ctx, obj1, "Promise", ctx->class_proto[JS_CLASS_PROMISE]); /* AsyncFunction */ ctx->class_proto[JS_CLASS_ASYNC_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto); obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor, "AsyncFunction", 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC, ctx->function_ctor); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_FUNCTION], js_async_function_proto_funcs, countof(js_async_function_proto_funcs)); JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_FUNCTION], 0, JS_PROP_CONFIGURABLE); JS_FreeValue(ctx, obj1); /* AsyncIteratorPrototype */ ctx->async_iterator_proto = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->async_iterator_proto, js_async_iterator_proto_funcs, countof(js_async_iterator_proto_funcs)); /* AsyncFromSyncIteratorPrototype */ ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR] = JS_NewObjectProto(ctx, ctx->async_iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR], js_async_from_sync_iterator_proto_funcs, countof(js_async_from_sync_iterator_proto_funcs)); /* AsyncGeneratorPrototype */ ctx->class_proto[JS_CLASS_ASYNC_GENERATOR] = JS_NewObjectProto(ctx, ctx->async_iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR], js_async_generator_proto_funcs, countof(js_async_generator_proto_funcs)); /* AsyncGeneratorFunction */ ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto); obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor, "AsyncGeneratorFunction", 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC_GENERATOR, ctx->function_ctor); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION], js_async_generator_function_proto_funcs, countof(js_async_generator_function_proto_funcs)); JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION], ctx->class_proto[JS_CLASS_ASYNC_GENERATOR], JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE); JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION], 0, JS_PROP_CONFIGURABLE); JS_FreeValue(ctx, obj1); } /* URI handling */ static int string_get_hex(JSString *p, int k, int n) { int c = 0, h; while (n-- > 0) { if ((h = from_hex(string_get(p, k++))) < 0) return -1; c = (c << 4) | h; } return c; } static int isURIReserved(int c) { return c < 0x100 && memchr(";/?:@&=+$,#", c, sizeof(";/?:@&=+$,#") - 1) != NULL; } static int __attribute__((format(printf, 2, 3))) js_throw_URIError(JSContext *ctx, const char *fmt, ...) { va_list ap; va_start(ap, fmt); JS_ThrowError(ctx, JS_URI_ERROR, fmt, ap); va_end(ap); return -1; } static int hex_decode(JSContext *ctx, JSString *p, int k) { int c; if (k >= p->len || string_get(p, k) != '%') return js_throw_URIError(ctx, "expecting %%"); if (k + 2 >= p->len || (c = string_get_hex(p, k + 1, 2)) < 0) return js_throw_URIError(ctx, "expecting hex digit"); return c; } static JSValue js_global_decodeURI(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int isComponent) { JSValue str; StringBuffer b_s, *b = &b_s; JSString *p; int k, c, c1, n, c_min; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; string_buffer_init(ctx, b, 0); p = JS_VALUE_GET_STRING(str); for (k = 0; k < p->len;) { c = string_get(p, k); if (c == '%') { c = hex_decode(ctx, p, k); if (c < 0) goto fail; k += 3; if (c < 0x80) { if (!isComponent && isURIReserved(c)) { c = '%'; k -= 2; } } else { /* Decode URI-encoded UTF-8 sequence */ if (c >= 0xc0 && c <= 0xdf) { n = 1; c_min = 0x80; c &= 0x1f; } else if (c >= 0xe0 && c <= 0xef) { n = 2; c_min = 0x800; c &= 0xf; } else if (c >= 0xf0 && c <= 0xf7) { n = 3; c_min = 0x10000; c &= 0x7; } else { n = 0; c_min = 1; c = 0; } while (n-- > 0) { c1 = hex_decode(ctx, p, k); if (c1 < 0) goto fail; k += 3; if ((c1 & 0xc0) != 0x80) { c = 0; break; } c = (c << 6) | (c1 & 0x3f); } if (c < c_min || c > 0x10FFFF || (c >= 0xd800 && c < 0xe000)) { js_throw_URIError(ctx, "malformed UTF-8"); goto fail; } } } else { k++; } string_buffer_putc(b, c); } JS_FreeValue(ctx, str); return string_buffer_end(b); fail: JS_FreeValue(ctx, str); string_buffer_free(b); return JS_EXCEPTION; } static int isUnescaped(int c) { static char const unescaped_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "@*_+-./"; return c < 0x100 && memchr(unescaped_chars, c, sizeof(unescaped_chars) - 1); } static int isURIUnescaped(int c, int isComponent) { return c < 0x100 && ((c >= 0x61 && c <= 0x7a) || (c >= 0x41 && c <= 0x5a) || (c >= 0x30 && c <= 0x39) || memchr("-_.!~*'()", c, sizeof("-_.!~*'()") - 1) != NULL || (!isComponent && isURIReserved(c))); } static int encodeURI_hex(StringBuffer *b, int c) { uint8_t buf[6]; int n = 0; const char *hex = "0123456789ABCDEF"; buf[n++] = '%'; if (c >= 256) { buf[n++] = 'u'; buf[n++] = hex[(c >> 12) & 15]; buf[n++] = hex[(c >> 8) & 15]; } buf[n++] = hex[(c >> 4) & 15]; buf[n++] = hex[(c >> 0) & 15]; return string_buffer_write8(b, buf, n); } static JSValue js_global_encodeURI(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int isComponent) { JSValue str; StringBuffer b_s, *b = &b_s; JSString *p; int k, c, c1; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; p = JS_VALUE_GET_STRING(str); string_buffer_init(ctx, b, p->len); for (k = 0; k < p->len;) { c = string_get(p, k); k++; if (isURIUnescaped(c, isComponent)) { string_buffer_putc16(b, c); } else { if (c >= 0xdc00 && c <= 0xdfff) { js_throw_URIError(ctx, "invalid character"); goto fail; } else if (c >= 0xd800 && c <= 0xdbff) { if (k >= p->len) { js_throw_URIError(ctx, "expecting surrogate pair"); goto fail; } c1 = string_get(p, k); k++; if (c1 < 0xdc00 || c1 > 0xdfff) { js_throw_URIError(ctx, "expecting surrogate pair"); goto fail; } c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000; } if (c < 0x80) { encodeURI_hex(b, c); } else { /* XXX: use C UTF-8 conversion ? */ if (c < 0x800) { encodeURI_hex(b, (c >> 6) | 0xc0); } else { if (c < 0x10000) { encodeURI_hex(b, (c >> 12) | 0xe0); } else { encodeURI_hex(b, (c >> 18) | 0xf0); encodeURI_hex(b, ((c >> 12) & 0x3f) | 0x80); } encodeURI_hex(b, ((c >> 6) & 0x3f) | 0x80); } encodeURI_hex(b, (c & 0x3f) | 0x80); } } } JS_FreeValue(ctx, str); return string_buffer_end(b); fail: JS_FreeValue(ctx, str); string_buffer_free(b); return JS_EXCEPTION; } static JSValue js_global_escape(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str; StringBuffer b_s, *b = &b_s; JSString *p; int i, len, c; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; p = JS_VALUE_GET_STRING(str); string_buffer_init(ctx, b, p->len); for (i = 0, len = p->len; i < len; i++) { c = string_get(p, i); if (isUnescaped(c)) { string_buffer_putc16(b, c); } else { encodeURI_hex(b, c); } } JS_FreeValue(ctx, str); return string_buffer_end(b); } static JSValue js_global_unescape(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue str; StringBuffer b_s, *b = &b_s; JSString *p; int i, len, c, n; str = JS_ToString(ctx, argv[0]); if (JS_IsException(str)) return str; string_buffer_init(ctx, b, 0); p = JS_VALUE_GET_STRING(str); for (i = 0, len = p->len; i < len; i++) { c = string_get(p, i); if (c == '%') { if (i + 6 <= len && string_get(p, i + 1) == 'u' && (n = string_get_hex(p, i + 2, 4)) >= 0) { c = n; i += 6 - 1; } else if (i + 3 <= len && (n = string_get_hex(p, i + 1, 2)) >= 0) { c = n; i += 3 - 1; } } string_buffer_putc16(b, c); } JS_FreeValue(ctx, str); return string_buffer_end(b); } /* global object */ static const JSCFunctionListEntry js_global_funcs[] = { JS_CFUNC_DEF("parseInt", 2, js_parseInt ), JS_CFUNC_DEF("parseFloat", 1, js_parseFloat ), JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ), JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ), JS_CFUNC_MAGIC_DEF("decodeURI", 1, js_global_decodeURI, 0 ), JS_CFUNC_MAGIC_DEF("decodeURIComponent", 1, js_global_decodeURI, 1 ), JS_CFUNC_MAGIC_DEF("encodeURI", 1, js_global_encodeURI, 0 ), JS_CFUNC_MAGIC_DEF("encodeURIComponent", 1, js_global_encodeURI, 1 ), JS_CFUNC_DEF("escape", 1, js_global_escape ), JS_CFUNC_DEF("unescape", 1, js_global_unescape ), JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ), JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), JS_PROP_UNDEFINED_DEF("undefined", 0 ), /* for the 'Date' implementation */ JS_CFUNC_DEF("__date_clock", 0, js___date_clock ), //JS_CFUNC_DEF("__date_now", 0, js___date_now ), //JS_CFUNC_DEF("__date_getTimezoneOffset", 1, js___date_getTimezoneOffset ), //JS_CFUNC_DEF("__date_create", 3, js___date_create ), }; /* Date */ static int64_t math_mod(int64_t a, int64_t b) { /* return positive modulo */ int64_t m = a % b; return m + (m < 0) * b; } static int64_t floor_div(int64_t a, int64_t b) { /* integer division rounding toward -Infinity */ int64_t m = a % b; return (a - (m + (m < 0) * b)) / b; } static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static __exception int JS_ThisTimeValue(JSContext *ctx, double *valp, JSValueConst 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_DATE && JS_IsNumber(p->u.object_data)) return JS_ToFloat64(ctx, valp, p->u.object_data); } JS_ThrowTypeError(ctx, "not a Date object"); return -1; } static JSValue JS_SetThisTimeValue(JSContext *ctx, JSValueConst this_val, double v) { if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(this_val); if (p->class_id == JS_CLASS_DATE) { JS_FreeValue(ctx, p->u.object_data); p->u.object_data = __JS_NewFloat64(ctx, v); return JS_DupValue(ctx, p->u.object_data); } } return JS_ThrowTypeError(ctx, "not a Date object"); } static int64_t days_from_year(int64_t y) { return 365 * (y - 1970) + floor_div(y - 1969, 4) - floor_div(y - 1901, 100) + floor_div(y - 1601, 400); } static int64_t days_in_year(int64_t y) { return 365 + !(y % 4) - !(y % 100) + !(y % 400); } /* return the year, update days */ static int64_t year_from_days(int64_t *days) { int64_t y, d1, nd, d = *days; y = floor_div(d * 10000, 3652425) + 1970; /* the initial approximation is very good, so only a few iterations are necessary */ for(;;) { d1 = d - days_from_year(y); if (d1 < 0) { y--; d1 += days_in_year(y); } else { nd = days_in_year(y); if (d1 < nd) break; d1 -= nd; y++; } } *days = d1; return y; } static int const month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; static char const day_names[] = "SunMonTueWedThuFriSat"; static __exception int get_date_fields(JSContext *ctx, JSValueConst obj, int64_t fields[9], int is_local, int force) { double dval; int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0; if (JS_ThisTimeValue(ctx, &dval, obj)) return -1; if (isnan(dval)) { if (!force) return FALSE; /* NaN */ d = 0; /* initialize all fields to 0 */ } else { d = dval; if (is_local) { tz = -getTimezoneOffset(d); d += tz * 60000; } } /* result is >= 0, we can use % */ h = math_mod(d, 86400000); days = (d - h) / 86400000; ms = h % 1000; h = (h - ms) / 1000; s = h % 60; h = (h - s) / 60; m = h % 60; h = (h - m) / 60; wd = math_mod(days + 4, 7); /* week day */ y = year_from_days(&days); for(i = 0; i < 11; i++) { md = month_days[i]; if (i == 1) md += days_in_year(y) - 365; if (days < md) break; days -= md; } fields[0] = y; fields[1] = i; fields[2] = days + 1; fields[3] = h; fields[4] = m; fields[5] = s; fields[6] = ms; fields[7] = wd; fields[8] = tz; return TRUE; } static double time_clip(double t) { if (t >= -8.64e15 && t <= 8.64e15) return trunc(t) + 0.0; /* convert -0 to +0 */ else return NAN; } static double set_date_fields(int64_t fields[], int is_local) { int64_t days, y, m, md, h, d, i; i = fields[1]; m = math_mod(i, 12); y = fields[0] + (i - m) / 12; days = days_from_year(y); for(i = 0; i < m; i++) { md = month_days[i]; if (i == 1) md += days_in_year(y) - 365; days += md; } days += fields[2] - 1; h = ((fields[3] * 60 + fields[4]) * 60 + fields[5]) * 1000 + fields[6]; d = days * 86400000 + h; if (is_local) d += getTimezoneOffset(d) * 60000; return time_clip(d); } static JSValue get_date_field(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { // get_date_field(obj, n, is_local) int64_t fields[9]; int res, n, is_local; is_local = magic & 0x0F; n = (magic >> 4) & 0x0F; res = get_date_fields(ctx, this_val, fields, is_local, 0); if (res < 0) return JS_EXCEPTION; if (!res) return JS_NAN; if (magic & 0x100) { // getYear fields[0] -= 1900; } return JS_NewInt64(ctx, fields[n]); } static JSValue set_date_field(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { // _field(obj, first_field, end_field, args, is_local) int64_t fields[9]; int res, first_field, end_field, is_local, i, n; double d, a; d = NAN; first_field = (magic >> 8) & 0x0F; end_field = (magic >> 4) & 0x0F; is_local = magic & 0x0F; res = get_date_fields(ctx, this_val, fields, is_local, first_field == 0); if (res < 0) return JS_EXCEPTION; if (res && argc > 0) { n = end_field - first_field; if (argc < n) n = argc; for(i = 0; i < n; i++) { if (JS_ToFloat64(ctx, &a, argv[i])) return JS_EXCEPTION; if (!isfinite(a)) goto done; fields[first_field + i] = trunc(a); } d = set_date_fields(fields, is_local); } done: return JS_SetThisTimeValue(ctx, this_val, d); } /* fmt: 0: toUTCString: "Tue, 02 Jan 2018 23:04:46 GMT" 1: toString: "Wed Jan 03 2018 00:05:22 GMT+0100 (CET)" 2: toISOString: "2018-01-02T23:02:56.927Z" 3: toLocaleString: "1/2/2018, 11:40:40 PM" part: 1=date, 2=time 3=all XXX: should use a variant of strftime(). */ static JSValue get_date_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { // _string(obj, fmt, part) char buf[64]; int64_t fields[9]; int res, fmt, part, pos; int y, mon, d, h, m, s, ms, wd, tz; fmt = (magic >> 4) & 0x0F; part = magic & 0x0F; res = get_date_fields(ctx, this_val, fields, fmt & 1, 0); if (res < 0) return JS_EXCEPTION; if (!res) { if (fmt == 2) return JS_ThrowRangeError(ctx, "Date value is NaN"); else return JS_NewString(ctx, "Invalid Date"); } y = fields[0]; mon = fields[1]; d = fields[2]; h = fields[3]; m = fields[4]; s = fields[5]; ms = fields[6]; wd = fields[7]; tz = fields[8]; pos = 0; if (part & 1) { /* date part */ switch(fmt) { case 0: pos += snprintf(buf + pos, sizeof(buf) - pos, "%.3s, %02d %.3s %0*d ", day_names + wd * 3, d, month_names + mon * 3, 4 + (y < 0), y); break; case 1: pos += snprintf(buf + pos, sizeof(buf) - pos, "%.3s %.3s %02d %0*d", day_names + wd * 3, month_names + mon * 3, d, 4 + (y < 0), y); if (part == 3) { buf[pos++] = ' '; } break; case 2: if (y >= 0 && y <= 9999) { pos += snprintf(buf + pos, sizeof(buf) - pos, "%04d", y); } else { pos += snprintf(buf + pos, sizeof(buf) - pos, "%+07d", y); } pos += snprintf(buf + pos, sizeof(buf) - pos, "-%02d-%02dT", mon + 1, d); break; case 3: pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d/%02d/%0*d", mon + 1, d, 4 + (y < 0), y); if (part == 3) { buf[pos++] = ','; buf[pos++] = ' '; } break; } } if (part & 2) { /* time part */ switch(fmt) { case 0: pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d GMT", h, m, s); break; case 1: pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d GMT", h, m, s); if (tz < 0) { buf[pos++] = '-'; tz = -tz; } else { buf[pos++] = '+'; } /* tz is >= 0, can use % */ pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d%02d", tz / 60, tz % 60); /* XXX: tack the time zone code? */ break; case 2: pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d.%03dZ", h, m, s, ms); break; case 3: pos += snprintf(buf + pos, sizeof(buf) - pos, "%02d:%02d:%02d %cM", (h + 1) % 12 - 1, m, s, (h < 12) ? 'A' : 'P'); break; } } return JS_NewStringLen(ctx, buf, pos); } /* OS dependent: return the UTC time in ms since 1970. */ static int64_t date_now(void) { struct timeval tv; gettimeofday(&tv, NULL); return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); } static JSValue js_date_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { // Date(y, mon, d, h, m, s, ms) JSValue rv; int i, n; double a, val; if (JS_IsUndefined(new_target)) { /* invoked as function */ argc = 0; } n = argc; if (n == 0) { val = date_now(); } else if (n == 1) { JSValue v, dv; if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) { JSObject *p = JS_VALUE_GET_OBJ(argv[0]); if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data)) { if (JS_ToFloat64(ctx, &val, p->u.object_data)) return JS_EXCEPTION; val = time_clip(val); goto has_val; } } v = JS_ToPrimitive(ctx, argv[0], HINT_NONE); if (JS_IsString(v)) { dv = js_Date_parse(ctx, JS_UNDEFINED, 1, (JSValueConst *)&v); JS_FreeValue(ctx, v); if (JS_IsException(dv)) return JS_EXCEPTION; if (JS_ToFloat64Free(ctx, &val, dv)) return JS_EXCEPTION; } else { if (JS_ToFloat64Free(ctx, &val, v)) return JS_EXCEPTION; } val = time_clip(val); } else { int64_t fields[] = { 0, 0, 1, 0, 0, 0, 0 }; if (n > 7) n = 7; for(i = 0; i < n; i++) { if (JS_ToFloat64(ctx, &a, argv[i])) return JS_EXCEPTION; if (!isfinite(a)) break; fields[i] = trunc(a); if (i == 0 && fields[0] >= 0 && fields[0] < 100) fields[0] += 1900; } val = (i == n) ? set_date_fields(fields, 1) : NAN; } has_val: #if 0 JSValueConst args[3]; args[0] = new_target; args[1] = ctx->class_proto[JS_CLASS_DATE]; args[2] = __JS_NewFloat64(ctx, val); rv = js___date_create(ctx, JS_UNDEFINED, 3, args); #else rv = js_create_from_ctor(ctx, new_target, JS_CLASS_DATE); if (!JS_IsException(rv)) JS_SetObjectData(ctx, rv, __JS_NewFloat64(ctx, val)); #endif if (!JS_IsException(rv) && JS_IsUndefined(new_target)) { /* invoked as a function, return (new Date()).toString(); */ JSValue s; s = get_date_string(ctx, rv, 0, NULL, 0x13); JS_FreeValue(ctx, rv); rv = s; } return rv; } static JSValue js_Date_UTC(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // UTC(y, mon, d, h, m, s, ms) int64_t fields[] = { 0, 0, 1, 0, 0, 0, 0 }; int i, n; double a; n = argc; if (n == 0) return JS_NAN; if (n > 7) n = 7; for(i = 0; i < n; i++) { if (JS_ToFloat64(ctx, &a, argv[i])) return JS_EXCEPTION; if (!isfinite(a)) return JS_NAN; fields[i] = trunc(a); if (i == 0 && fields[0] >= 0 && fields[0] < 100) fields[0] += 1900; } return __JS_NewFloat64(ctx, set_date_fields(fields, 0)); } static void string_skip_spaces(JSString *sp, int *pp) { while (*pp < sp->len && string_get(sp, *pp) == ' ') *pp += 1; } static void string_skip_non_spaces(JSString *sp, int *pp) { while (*pp < sp->len && string_get(sp, *pp) != ' ') *pp += 1; } /* parse a numeric field */ static int string_get_field(JSString *sp, int *pp, int64_t *pval) { int64_t v = 0; int c, p = *pp; /* skip non digits, should only skip spaces? */ while (p < sp->len) { c = string_get(sp, p); if (c >= '0' && c <= '9') break; p++; } if (p >= sp->len) return -1; while (p < sp->len) { c = string_get(sp, p); if (!(c >= '0' && c <= '9')) break; v = v * 10 + c - '0'; p++; } *pval = v; *pp = p; return 0; } /* parse a fixed width numeric field */ static int string_get_digits(JSString *sp, int *pp, int n, int64_t *pval) { int64_t v = 0; int i, c, p = *pp; for(i = 0; i < n; i++) { if (p >= sp->len) return -1; c = string_get(sp, p); if (!(c >= '0' && c <= '9')) return -1; v = v * 10 + c - '0'; p++; } *pval = v; *pp = p; return 0; } /* parse a signed numeric field */ static int string_get_signed_field(JSString *sp, int *pp, int64_t *pval) { int sgn, res; if (*pp >= sp->len) return -1; sgn = string_get(sp, *pp); if (sgn == '-' || sgn == '+') *pp += 1; res = string_get_field(sp, pp, pval); if (res == 0 && sgn == '-') *pval = -*pval; return res; } static int find_abbrev(JSString *sp, int p, const char *list, int count) { int n, i; if (p + 3 <= sp->len) { for (n = 0; n < count; n++) { for (i = 0; i < 3; i++) { if (string_get(sp, p + i) != month_names[n * 3 + i]) goto next; } return n; next:; } } return -1; } static int string_get_month(JSString *sp, int *pp, int64_t *pval) { int n; string_skip_spaces(sp, pp); n = find_abbrev(sp, *pp, month_names, 12); if (n < 0) return -1; *pval = n; *pp += 3; return 0; } static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // parse(s) JSValue s, rv; int64_t fields[] = { 0, 1, 1, 0, 0, 0, 0 }; int64_t tz, hh, mm; double d; int p, i, c, sgn; JSString *sp; BOOL is_local; rv = JS_NAN; s = JS_ToString(ctx, argv[0]); if (JS_IsException(s)) return JS_EXCEPTION; sp = JS_VALUE_GET_STRING(s); p = 0; if (p < sp->len && (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) { /* ISO format */ /* year field can be negative */ /* XXX: could be stricter */ if (string_get_signed_field(sp, &p, &fields[0])) goto done; is_local = TRUE; for (i = 1; i < 6; i++) { if (string_get_field(sp, &p, &fields[i])) break; } if (i <= 3) { /* no time: UTC by default */ is_local = FALSE; } else if (i == 6 && p < sp->len && string_get(sp, p) == '.') { /* parse milliseconds as a fractional part, round to nearest */ /* XXX: the spec does not indicate which rounding should be used */ int mul = 1000, ms = 0; while (++p < sp->len) { int c = string_get(sp, p); if (!(c >= '0' && c <= '9')) break; if (mul == 1 && c >= '5') ms += 1; ms += (c - '0') * (mul /= 10); } fields[6] = ms; } fields[1] -= 1; /* parse the time zone offset if present: [+-]HH:mm */ tz = 0; if (p < sp->len) { sgn = string_get(sp, p); if (sgn == '+' || sgn == '-') { if (string_get_field(sp, &p, &hh)) goto done; if (string_get_field(sp, &p, &mm)) goto done; tz = hh * 60 + mm; if (sgn == '-') tz = -tz; is_local = FALSE; } else if (sgn == 'Z') { is_local = FALSE; } } } else { /* toString or toUTCString format */ /* skip the day of the week */ string_skip_non_spaces(sp, &p); string_skip_spaces(sp, &p); if (p >= sp->len) goto done; c = string_get(sp, p); if (c >= '0' && c <= '9') { /* day of month first */ if (string_get_field(sp, &p, &fields[2])) goto done; if (string_get_month(sp, &p, &fields[1])) goto done; } else { /* month first */ if (string_get_month(sp, &p, &fields[1])) goto done; if (string_get_field(sp, &p, &fields[2])) goto done; } string_skip_spaces(sp, &p); if (string_get_signed_field(sp, &p, &fields[0])) goto done; /* hour, min, seconds */ for(i = 0; i < 3; i++) { if (string_get_field(sp, &p, &fields[3 + i])) goto done; } // XXX: parse optional milliseconds? /* parse the time zone offset if present: [+-]HHmm */ is_local = FALSE; tz = 0; for (tz = 0; p < sp->len; p++) { sgn = string_get(sp, p); if (sgn == '+' || sgn == '-') { p++; if (string_get_digits(sp, &p, 2, &hh)) goto done; if (string_get_digits(sp, &p, 2, &mm)) goto done; tz = hh * 60 + mm; if (sgn == '-') tz = -tz; break; } } } d = set_date_fields(fields, is_local) - tz * 60000; rv = __JS_NewFloat64(ctx, d); done: JS_FreeValue(ctx, s); return rv; } static JSValue js_Date_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // now() return JS_NewInt64(ctx, date_now()); } static JSValue js_date_Symbol_toPrimitive(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // Symbol_toPrimitive(hint) JSValueConst obj = this_val; JSAtom hint = JS_ATOM_NULL; int hint_num; if (!JS_IsObject(obj)) return JS_ThrowTypeErrorNotAnObject(ctx); if (JS_IsString(argv[0])) { hint = JS_ValueToAtom(ctx, argv[0]); if (hint == JS_ATOM_NULL) return JS_EXCEPTION; JS_FreeAtom(ctx, hint); } switch (hint) { case JS_ATOM_number: #ifdef CONFIG_BIGNUM case JS_ATOM_integer: #endif hint_num = HINT_NUMBER; break; case JS_ATOM_string: case JS_ATOM_default: hint_num = HINT_STRING; break; default: return JS_ThrowTypeError(ctx, "invalid hint"); } return JS_ToPrimitive(ctx, obj, hint_num | HINT_FORCE_ORDINARY); } static JSValue js_date_getTimezoneOffset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // getTimezoneOffset() double v; if (JS_ThisTimeValue(ctx, &v, this_val)) return JS_EXCEPTION; if (isnan(v)) return JS_NAN; else return JS_NewInt64(ctx, getTimezoneOffset((int64_t)trunc(v))); } static JSValue js_date_getTime(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // getTime() double v; if (JS_ThisTimeValue(ctx, &v, this_val)) return JS_EXCEPTION; return __JS_NewFloat64(ctx, v); } static JSValue js_date_setTime(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // setTime(v) double v; if (JS_ThisTimeValue(ctx, &v, this_val) || JS_ToFloat64(ctx, &v, argv[0])) return JS_EXCEPTION; return JS_SetThisTimeValue(ctx, this_val, time_clip(v)); } static JSValue js_date_setYear(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // setYear(y) double y; JSValueConst args[1]; if (JS_ThisTimeValue(ctx, &y, this_val) || JS_ToFloat64(ctx, &y, argv[0])) return JS_EXCEPTION; y = +y; if (isfinite(y)) { y = trunc(y); if (y >= 0 && y < 100) y += 1900; } args[0] = __JS_NewFloat64(ctx, y); return set_date_field(ctx, this_val, 1, args, 0x011); } static JSValue js_date_toJSON(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // toJSON(key) JSValue obj, tv, method, rv; double d; rv = JS_EXCEPTION; tv = JS_UNDEFINED; obj = JS_ToObject(ctx, this_val); tv = JS_ToPrimitive(ctx, obj, HINT_NUMBER); if (JS_IsException(tv)) goto exception; if (JS_IsNumber(tv)) { if (JS_ToFloat64(ctx, &d, tv) < 0) goto exception; if (!isfinite(d)) { rv = JS_NULL; goto done; } } method = JS_GetPropertyStr(ctx, obj, "toISOString"); if (JS_IsException(method)) goto exception; if (!JS_IsFunction(ctx, method)) { JS_ThrowTypeError(ctx, "object needs toISOString method"); JS_FreeValue(ctx, method); goto exception; } rv = JS_CallFree(ctx, method, obj, 0, NULL); exception: done: JS_FreeValue(ctx, obj); JS_FreeValue(ctx, tv); return rv; } static const JSCFunctionListEntry js_date_funcs[] = { JS_CFUNC_DEF("now", 0, js_Date_now ), JS_CFUNC_DEF("parse", 1, js_Date_parse ), JS_CFUNC_DEF("UTC", 7, js_Date_UTC ), }; static const JSCFunctionListEntry js_date_proto_funcs[] = { JS_CFUNC_DEF("valueOf", 0, js_date_getTime ), JS_CFUNC_MAGIC_DEF("toString", 0, get_date_string, 0x13 ), JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_date_Symbol_toPrimitive ), JS_CFUNC_MAGIC_DEF("toUTCString", 0, get_date_string, 0x03 ), JS_ALIAS_DEF("toGMTString", "toUTCString" ), JS_CFUNC_MAGIC_DEF("toISOString", 0, get_date_string, 0x23 ), JS_CFUNC_MAGIC_DEF("toDateString", 0, get_date_string, 0x11 ), JS_CFUNC_MAGIC_DEF("toTimeString", 0, get_date_string, 0x12 ), JS_CFUNC_MAGIC_DEF("toLocaleString", 0, get_date_string, 0x33 ), JS_CFUNC_MAGIC_DEF("toLocaleDateString", 0, get_date_string, 0x31 ), JS_CFUNC_MAGIC_DEF("toLocaleTimeString", 0, get_date_string, 0x32 ), JS_CFUNC_DEF("getTimezoneOffset", 0, js_date_getTimezoneOffset ), JS_CFUNC_DEF("getTime", 0, js_date_getTime ), JS_CFUNC_MAGIC_DEF("getYear", 0, get_date_field, 0x101 ), JS_CFUNC_MAGIC_DEF("getFullYear", 0, get_date_field, 0x01 ), JS_CFUNC_MAGIC_DEF("getUTCFullYear", 0, get_date_field, 0x00 ), JS_CFUNC_MAGIC_DEF("getMonth", 0, get_date_field, 0x11 ), JS_CFUNC_MAGIC_DEF("getUTCMonth", 0, get_date_field, 0x10 ), JS_CFUNC_MAGIC_DEF("getDate", 0, get_date_field, 0x21 ), JS_CFUNC_MAGIC_DEF("getUTCDate", 0, get_date_field, 0x20 ), JS_CFUNC_MAGIC_DEF("getHours", 0, get_date_field, 0x31 ), JS_CFUNC_MAGIC_DEF("getUTCHours", 0, get_date_field, 0x30 ), JS_CFUNC_MAGIC_DEF("getMinutes", 0, get_date_field, 0x41 ), JS_CFUNC_MAGIC_DEF("getUTCMinutes", 0, get_date_field, 0x40 ), JS_CFUNC_MAGIC_DEF("getSeconds", 0, get_date_field, 0x51 ), JS_CFUNC_MAGIC_DEF("getUTCSeconds", 0, get_date_field, 0x50 ), JS_CFUNC_MAGIC_DEF("getMilliseconds", 0, get_date_field, 0x61 ), JS_CFUNC_MAGIC_DEF("getUTCMilliseconds", 0, get_date_field, 0x60 ), JS_CFUNC_MAGIC_DEF("getDay", 0, get_date_field, 0x71 ), JS_CFUNC_MAGIC_DEF("getUTCDay", 0, get_date_field, 0x70 ), JS_CFUNC_DEF("setTime", 1, js_date_setTime ), JS_CFUNC_MAGIC_DEF("setMilliseconds", 1, set_date_field, 0x671 ), JS_CFUNC_MAGIC_DEF("setUTCMilliseconds", 1, set_date_field, 0x670 ), JS_CFUNC_MAGIC_DEF("setSeconds", 2, set_date_field, 0x571 ), JS_CFUNC_MAGIC_DEF("setUTCSeconds", 2, set_date_field, 0x570 ), JS_CFUNC_MAGIC_DEF("setMinutes", 3, set_date_field, 0x471 ), JS_CFUNC_MAGIC_DEF("setUTCMinutes", 3, set_date_field, 0x470 ), JS_CFUNC_MAGIC_DEF("setHours", 4, set_date_field, 0x371 ), JS_CFUNC_MAGIC_DEF("setUTCHours", 4, set_date_field, 0x370 ), JS_CFUNC_MAGIC_DEF("setDate", 1, set_date_field, 0x231 ), JS_CFUNC_MAGIC_DEF("setUTCDate", 1, set_date_field, 0x230 ), JS_CFUNC_MAGIC_DEF("setMonth", 2, set_date_field, 0x131 ), JS_CFUNC_MAGIC_DEF("setUTCMonth", 2, set_date_field, 0x130 ), JS_CFUNC_DEF("setYear", 1, js_date_setYear ), JS_CFUNC_MAGIC_DEF("setFullYear", 3, set_date_field, 0x031 ), JS_CFUNC_MAGIC_DEF("setUTCFullYear", 3, set_date_field, 0x030 ), JS_CFUNC_DEF("toJSON", 1, js_date_toJSON ), }; void JS_AddIntrinsicDate(JSContext *ctx) { JSValueConst obj; /* Date */ ctx->class_proto[JS_CLASS_DATE] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATE], js_date_proto_funcs, countof(js_date_proto_funcs)); obj = JS_NewGlobalCConstructor(ctx, "Date", js_date_constructor, 7, ctx->class_proto[JS_CLASS_DATE]); JS_SetPropertyFunctionList(ctx, obj, js_date_funcs, countof(js_date_funcs)); } /* eval */ void JS_AddIntrinsicEval(JSContext *ctx) { ctx->eval_internal = __JS_EvalInternal; } #ifdef CONFIG_BIGNUM /* Operators */ static void js_operator_set_finalizer(JSRuntime *rt, JSValue val) { JSOperatorSetData *opset = JS_GetOpaque(val, JS_CLASS_OPERATOR_SET); int i, j; JSBinaryOperatorDefEntry *ent; if (opset) { for(i = 0; i < JS_OVOP_COUNT; i++) { if (opset->self_ops[i]) JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, opset->self_ops[i])); } for(j = 0; j < opset->left.count; j++) { ent = &opset->left.tab[j]; for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { if (ent->ops[i]) JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ent->ops[i])); } } js_free_rt(rt, opset->left.tab); for(j = 0; j < opset->right.count; j++) { ent = &opset->right.tab[j]; for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { if (ent->ops[i]) JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ent->ops[i])); } } js_free_rt(rt, opset->right.tab); js_free_rt(rt, opset); } } static void js_operator_set_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSOperatorSetData *opset = JS_GetOpaque(val, JS_CLASS_OPERATOR_SET); int i, j; JSBinaryOperatorDefEntry *ent; if (opset) { for(i = 0; i < JS_OVOP_COUNT; i++) { if (opset->self_ops[i]) JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, opset->self_ops[i]), mark_func); } for(j = 0; j < opset->left.count; j++) { ent = &opset->left.tab[j]; for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { if (ent->ops[i]) JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ent->ops[i]), mark_func); } } for(j = 0; j < opset->right.count; j++) { ent = &opset->right.tab[j]; for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { if (ent->ops[i]) JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ent->ops[i]), mark_func); } } } } /* create an OperatorSet object */ static JSValue js_operators_create_internal(JSContext *ctx, int argc, JSValueConst *argv, BOOL is_primitive) { JSValue opset_obj, prop, obj; JSOperatorSetData *opset, *opset1; JSBinaryOperatorDef *def; JSValueConst arg; int i, j; JSBinaryOperatorDefEntry *new_tab; JSBinaryOperatorDefEntry *ent; uint32_t op_count; if (ctx->rt->operator_count == UINT32_MAX) { return JS_ThrowTypeError(ctx, "too many operators"); } opset_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_OPERATOR_SET); if (JS_IsException(opset_obj)) goto fail; opset = js_mallocz(ctx, sizeof(*opset)); if (!opset) goto fail; JS_SetOpaque(opset_obj, opset); if (argc >= 1) { arg = argv[0]; /* self operators */ for(i = 0; i < JS_OVOP_COUNT; i++) { prop = JS_GetPropertyStr(ctx, arg, js_overloadable_operator_names[i]); if (JS_IsException(prop)) goto fail; if (!JS_IsUndefined(prop)) { if (check_function(ctx, prop)) { JS_FreeValue(ctx, prop); goto fail; } opset->self_ops[i] = JS_VALUE_GET_OBJ(prop); } } } /* left & right operators */ for(j = 1; j < argc; j++) { arg = argv[j]; prop = JS_GetPropertyStr(ctx, arg, "left"); if (JS_IsException(prop)) goto fail; def = &opset->right; if (JS_IsUndefined(prop)) { prop = JS_GetPropertyStr(ctx, arg, "right"); if (JS_IsException(prop)) goto fail; if (JS_IsUndefined(prop)) { JS_ThrowTypeError(ctx, "left or right property must be present"); goto fail; } def = &opset->left; } /* get the operator set */ obj = JS_GetProperty(ctx, prop, JS_ATOM_prototype); JS_FreeValue(ctx, prop); if (JS_IsException(obj)) goto fail; prop = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_operatorSet); JS_FreeValue(ctx, obj); if (JS_IsException(prop)) goto fail; opset1 = JS_GetOpaque2(ctx, prop, JS_CLASS_OPERATOR_SET); if (!opset1) { JS_FreeValue(ctx, prop); goto fail; } op_count = opset1->operator_counter; JS_FreeValue(ctx, prop); /* we assume there are few entries */ new_tab = js_realloc(ctx, def->tab, (def->count + 1) * sizeof(def->tab[0])); if (!new_tab) goto fail; def->tab = new_tab; def->count++; ent = def->tab + def->count - 1; memset(ent, 0, sizeof(def->tab[0])); ent->operator_index = op_count; for(i = 0; i < JS_OVOP_BINARY_COUNT; i++) { prop = JS_GetPropertyStr(ctx, arg, js_overloadable_operator_names[i]); if (JS_IsException(prop)) goto fail; if (!JS_IsUndefined(prop)) { if (check_function(ctx, prop)) { JS_FreeValue(ctx, prop); goto fail; } ent->ops[i] = JS_VALUE_GET_OBJ(prop); } } } opset->is_primitive = is_primitive; opset->operator_counter = ctx->rt->operator_count++; return opset_obj; fail: JS_FreeValue(ctx, opset_obj); return JS_EXCEPTION; } static JSValue js_operators_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_operators_create_internal(ctx, argc, argv, FALSE); } static JSValue js_operators_updateBigIntOperators(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue opset_obj, prop; JSOperatorSetData *opset; const JSOverloadableOperatorEnum ops[2] = { JS_OVOP_DIV, JS_OVOP_POW }; JSOverloadableOperatorEnum op; int i; opset_obj = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_BIG_INT], JS_ATOM_Symbol_operatorSet); if (JS_IsException(opset_obj)) goto fail; opset = JS_GetOpaque2(ctx, opset_obj, JS_CLASS_OPERATOR_SET); if (!opset) goto fail; for(i = 0; i < countof(ops); i++) { op = ops[i]; prop = JS_GetPropertyStr(ctx, argv[0], js_overloadable_operator_names[op]); if (JS_IsException(prop)) goto fail; if (!JS_IsUndefined(prop)) { if (!JS_IsNull(prop) && check_function(ctx, prop)) { JS_FreeValue(ctx, prop); goto fail; } if (opset->self_ops[op]) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, opset->self_ops[op])); if (JS_IsNull(prop)) { opset->self_ops[op] = NULL; } else { opset->self_ops[op] = JS_VALUE_GET_PTR(prop); } } } JS_FreeValue(ctx, opset_obj); return JS_UNDEFINED; fail: JS_FreeValue(ctx, opset_obj); return JS_EXCEPTION; } static int js_operators_set_default(JSContext *ctx, JSValueConst obj) { JSValue opset_obj; if (!JS_IsObject(obj)) /* in case the prototype is not defined */ return 0; opset_obj = js_operators_create_internal(ctx, 0, NULL, TRUE); if (JS_IsException(opset_obj)) return -1; /* cannot be modified by the user */ JS_DefinePropertyValue(ctx, obj, JS_ATOM_Symbol_operatorSet, opset_obj, 0); return 0; } static JSValue js_dummy_operators_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { return js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); } static JSValue js_global_operators(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue func_obj, proto, opset_obj; func_obj = JS_UNDEFINED; proto = JS_NewObject(ctx); if (JS_IsException(proto)) return JS_EXCEPTION; opset_obj = js_operators_create_internal(ctx, argc, argv, FALSE); if (JS_IsException(opset_obj)) goto fail; JS_DefinePropertyValue(ctx, proto, JS_ATOM_Symbol_operatorSet, opset_obj, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); func_obj = JS_NewCFunction2(ctx, js_dummy_operators_ctor, "Operators", 0, JS_CFUNC_constructor, 0); if (JS_IsException(func_obj)) goto fail; JS_SetConstructor2(ctx, func_obj, proto, 0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_FreeValue(ctx, proto); return func_obj; fail: JS_FreeValue(ctx, proto); JS_FreeValue(ctx, func_obj); return JS_EXCEPTION; } static const JSCFunctionListEntry js_operators_funcs[] = { JS_CFUNC_DEF("create", 1, js_operators_create ), JS_CFUNC_DEF("updateBigIntOperators", 2, js_operators_updateBigIntOperators ), }; /* must be called after all overloadable base types are initialized */ void JS_AddIntrinsicOperators(JSContext *ctx) { JSValue obj; ctx->allow_operator_overloading = TRUE; obj = JS_NewCFunction(ctx, js_global_operators, "Operators", 1); JS_SetPropertyFunctionList(ctx, obj, js_operators_funcs, countof(js_operators_funcs)); JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_Operators, obj, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); /* add default operatorSets */ js_operators_set_default(ctx, ctx->class_proto[JS_CLASS_BOOLEAN]); js_operators_set_default(ctx, ctx->class_proto[JS_CLASS_NUMBER]); js_operators_set_default(ctx, ctx->class_proto[JS_CLASS_STRING]); js_operators_set_default(ctx, ctx->class_proto[JS_CLASS_BIG_INT]); js_operators_set_default(ctx, ctx->class_proto[JS_CLASS_BIG_FLOAT]); js_operators_set_default(ctx, ctx->class_proto[JS_CLASS_BIG_DECIMAL]); } /* BigInt */ static JSValue JS_ToBigIntCtorFree(JSContext *ctx, JSValue val) { uint32_t tag; redo: tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_INT: case JS_TAG_BOOL: val = JS_NewBigInt64(ctx, JS_VALUE_GET_INT(val)); break; case JS_TAG_BIG_INT: break; case JS_TAG_FLOAT64: case JS_TAG_BIG_FLOAT: { bf_t *a, a_s; a = JS_ToBigFloat(ctx, &a_s, val); if (!bf_is_finite(a)) { JS_FreeValue(ctx, val); val = JS_ThrowRangeError(ctx, "cannot convert NaN or Infinity to bigint"); } else { JSValue val1 = JS_NewBigInt(ctx); bf_t *r; int ret; if (JS_IsException(val1)) { JS_FreeValue(ctx, val); return JS_EXCEPTION; } r = JS_GetBigInt(val1); ret = bf_set(r, a); ret |= bf_rint(r, BF_RNDZ); JS_FreeValue(ctx, val); if (ret & BF_ST_MEM_ERROR) { JS_FreeValue(ctx, val1); val = JS_ThrowOutOfMemory(ctx); } else if (ret & BF_ST_INEXACT) { JS_FreeValue(ctx, val1); val = JS_ThrowRangeError(ctx, "cannot convert to bigint: not an integer"); } else { val = JS_CompactBigInt(ctx, val1); } } if (a == &a_s) bf_delete(a); } break; case JS_TAG_BIG_DECIMAL: val = JS_ToStringFree(ctx, val); if (JS_IsException(val)) break; goto redo; case JS_TAG_STRING: val = JS_StringToBigIntErr(ctx, val); break; case JS_TAG_OBJECT: val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); if (JS_IsException(val)) break; goto redo; case JS_TAG_NULL: case JS_TAG_UNDEFINED: default: JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert to bigint"); } return val; } static JSValue js_bigint_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ToBigIntCtorFree(ctx, JS_DupValue(ctx, argv[0])); } static JSValue js_thisBigIntValue(JSContext *ctx, JSValueConst this_val) { if (JS_IsBigInt(ctx, this_val)) 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_BIG_INT) { if (JS_IsBigInt(ctx, p->u.object_data)) return JS_DupValue(ctx, p->u.object_data); } } return JS_ThrowTypeError(ctx, "not a bigint"); } static JSValue js_bigint_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; int base; JSValue ret; val = js_thisBigIntValue(ctx, this_val); if (JS_IsException(val)) return val; if (argc == 0 || JS_IsUndefined(argv[0])) { base = 10; } else { base = js_get_radix(ctx, argv[0]); if (base < 0) goto fail; } ret = js_bigint_to_string1(ctx, val, base); JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_bigint_valueOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_thisBigIntValue(ctx, this_val); } static JSValue js_bigint_div(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { bf_t a_s, b_s, *a, *b, *r, *q; int status; JSValue q_val, r_val; q_val = JS_NewBigInt(ctx); if (JS_IsException(q_val)) return JS_EXCEPTION; r_val = JS_NewBigInt(ctx); if (JS_IsException(r_val)) goto fail; b = NULL; a = JS_ToBigInt(ctx, &a_s, argv[0]); if (!a) goto fail; b = JS_ToBigInt(ctx, &b_s, argv[1]); if (!b) { JS_FreeBigInt(ctx, a, &a_s); goto fail; } q = JS_GetBigInt(q_val); r = JS_GetBigInt(r_val); status = bf_divrem(q, r, a, b, BF_PREC_INF, BF_RNDZ, magic & 0xf); JS_FreeBigInt(ctx, a, &a_s); JS_FreeBigInt(ctx, b, &b_s); if (unlikely(status)) { throw_bf_exception(ctx, status); goto fail; } q_val = JS_CompactBigInt(ctx, q_val); if (magic & 0x10) { JSValue ret; ret = JS_NewArray(ctx); if (JS_IsException(ret)) goto fail; JS_SetPropertyUint32(ctx, ret, 0, q_val); JS_SetPropertyUint32(ctx, ret, 1, JS_CompactBigInt(ctx, r_val)); return ret; } else { JS_FreeValue(ctx, r_val); return q_val; } fail: JS_FreeValue(ctx, q_val); JS_FreeValue(ctx, r_val); return JS_EXCEPTION; } static JSValue js_bigint_sqrt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { bf_t a_s, *a, *r, *rem; int status; JSValue r_val, rem_val; r_val = JS_NewBigInt(ctx); if (JS_IsException(r_val)) return JS_EXCEPTION; rem_val = JS_NewBigInt(ctx); if (JS_IsException(rem_val)) return JS_EXCEPTION; r = JS_GetBigInt(r_val); rem = JS_GetBigInt(rem_val); a = JS_ToBigInt(ctx, &a_s, argv[0]); if (!a) goto fail; status = bf_sqrtrem(r, rem, a); JS_FreeBigInt(ctx, a, &a_s); if (unlikely(status & ~BF_ST_INEXACT)) { throw_bf_exception(ctx, status); goto fail; } r_val = JS_CompactBigInt(ctx, r_val); if (magic) { JSValue ret; ret = JS_NewArray(ctx); if (JS_IsException(ret)) goto fail; JS_SetPropertyUint32(ctx, ret, 0, r_val); JS_SetPropertyUint32(ctx, ret, 1, JS_CompactBigInt(ctx, rem_val)); return ret; } else { JS_FreeValue(ctx, rem_val); return r_val; } fail: JS_FreeValue(ctx, r_val); JS_FreeValue(ctx, rem_val); return JS_EXCEPTION; } static JSValue js_bigint_op1(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { bf_t a_s, *a; int64_t res; a = JS_ToBigInt(ctx, &a_s, argv[0]); if (!a) return JS_EXCEPTION; switch(magic) { case 0: /* floorLog2 */ if (a->sign || a->expn <= 0) { res = -1; } else { res = a->expn - 1; } break; case 1: /* ctz */ if (bf_is_zero(a)) { res = -1; } else { res = bf_get_exp_min(a); } break; default: abort(); } JS_FreeBigInt(ctx, a, &a_s); return JS_NewBigInt64(ctx, res); } static JSValue js_bigint_asUintN(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int asIntN) { uint64_t bits; bf_t a_s, *a = &a_s, *r, mask_s, *mask = &mask_s; JSValue res; if (JS_ToIndex(ctx, &bits, argv[0])) return JS_EXCEPTION; res = JS_NewBigInt(ctx); if (JS_IsException(res)) return JS_EXCEPTION; r = JS_GetBigInt(res); a = JS_ToBigInt(ctx, &a_s, argv[1]); if (!a) { JS_FreeValue(ctx, res); return JS_EXCEPTION; } /* XXX: optimize */ r = JS_GetBigInt(res); bf_init(ctx->bf_ctx, mask); bf_set_ui(mask, 1); bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ); bf_add_si(mask, mask, -1, BF_PREC_INF, BF_RNDZ); bf_logic_and(r, a, mask); if (asIntN && bits != 0) { bf_set_ui(mask, 1); bf_mul_2exp(mask, bits - 1, BF_PREC_INF, BF_RNDZ); if (bf_cmpu(r, mask) >= 0) { bf_set_ui(mask, 1); bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ); bf_sub(r, r, mask, BF_PREC_INF, BF_RNDZ); } } bf_delete(mask); JS_FreeBigInt(ctx, a, &a_s); return JS_CompactBigInt(ctx, res); } static const JSCFunctionListEntry js_bigint_funcs[] = { JS_CFUNC_MAGIC_DEF("asUintN", 2, js_bigint_asUintN, 0 ), JS_CFUNC_MAGIC_DEF("asIntN", 2, js_bigint_asUintN, 1 ), /* QuickJS extensions */ JS_CFUNC_MAGIC_DEF("tdiv", 2, js_bigint_div, BF_RNDZ ), JS_CFUNC_MAGIC_DEF("fdiv", 2, js_bigint_div, BF_RNDD ), JS_CFUNC_MAGIC_DEF("cdiv", 2, js_bigint_div, BF_RNDU ), JS_CFUNC_MAGIC_DEF("ediv", 2, js_bigint_div, BF_DIVREM_EUCLIDIAN ), JS_CFUNC_MAGIC_DEF("tdivrem", 2, js_bigint_div, BF_RNDZ | 0x10 ), JS_CFUNC_MAGIC_DEF("fdivrem", 2, js_bigint_div, BF_RNDD | 0x10 ), JS_CFUNC_MAGIC_DEF("cdivrem", 2, js_bigint_div, BF_RNDU | 0x10 ), JS_CFUNC_MAGIC_DEF("edivrem", 2, js_bigint_div, BF_DIVREM_EUCLIDIAN | 0x10 ), JS_CFUNC_MAGIC_DEF("sqrt", 1, js_bigint_sqrt, 0 ), JS_CFUNC_MAGIC_DEF("sqrtrem", 1, js_bigint_sqrt, 1 ), JS_CFUNC_MAGIC_DEF("floorLog2", 1, js_bigint_op1, 0 ), JS_CFUNC_MAGIC_DEF("ctz", 1, js_bigint_op1, 1 ), }; static const JSCFunctionListEntry js_bigint_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_bigint_toString ), JS_CFUNC_DEF("valueOf", 0, js_bigint_valueOf ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "BigInt", JS_PROP_CONFIGURABLE ), }; void JS_AddIntrinsicBigInt(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; rt->bigint_ops.to_string = js_bigint_to_string; rt->bigint_ops.from_string = js_string_to_bigint; rt->bigint_ops.unary_arith = js_unary_arith_bigint; rt->bigint_ops.binary_arith = js_binary_arith_bigint; rt->bigint_ops.compare = js_compare_bigfloat; ctx->class_proto[JS_CLASS_BIG_INT] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_INT], js_bigint_proto_funcs, countof(js_bigint_proto_funcs)); obj1 = JS_NewCFunction(ctx, js_bigint_constructor, "BigInt", 1); JS_NewGlobalCConstructor2(ctx, obj1, "BigInt", ctx->class_proto[JS_CLASS_BIG_INT]); JS_SetPropertyFunctionList(ctx, obj1, js_bigint_funcs, countof(js_bigint_funcs)); } /* BigFloat */ static JSValue js_thisBigFloatValue(JSContext *ctx, JSValueConst this_val) { if (JS_IsBigFloat(this_val)) 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_BIG_FLOAT) { if (JS_IsBigFloat(p->u.object_data)) return JS_DupValue(ctx, p->u.object_data); } } return JS_ThrowTypeError(ctx, "not a bigfloat"); } static JSValue js_bigfloat_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; int base; JSValue ret; val = js_thisBigFloatValue(ctx, this_val); if (JS_IsException(val)) return val; if (argc == 0 || JS_IsUndefined(argv[0])) { base = 10; } else { base = js_get_radix(ctx, argv[0]); if (base < 0) goto fail; } ret = js_ftoa(ctx, val, base, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN); JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_bigfloat_valueOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_thisBigFloatValue(ctx, this_val); } static int bigfloat_get_rnd_mode(JSContext *ctx, JSValueConst val) { int rnd_mode; if (JS_ToInt32Sat(ctx, &rnd_mode, val)) return -1; if (rnd_mode < BF_RNDN || rnd_mode > BF_RNDF) { JS_ThrowRangeError(ctx, "invalid rounding mode"); return -1; } return rnd_mode; } static JSValue js_bigfloat_toFixed(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; int64_t f; int rnd_mode, radix; val = js_thisBigFloatValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_ToInt64Sat(ctx, &f, argv[0])) goto fail; if (f < 0 || f > BF_PREC_MAX) { JS_ThrowRangeError(ctx, "invalid number of digits"); goto fail; } rnd_mode = BF_RNDNA; radix = 10; /* XXX: swap parameter order for rounding mode and radix */ if (argc > 1) { rnd_mode = bigfloat_get_rnd_mode(ctx, argv[1]); if (rnd_mode < 0) goto fail; } if (argc > 2) { radix = js_get_radix(ctx, argv[2]); if (radix < 0) goto fail; } ret = js_ftoa(ctx, val, radix, f, rnd_mode | BF_FTOA_FORMAT_FRAC); JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static BOOL js_bigfloat_is_finite(JSContext *ctx, JSValueConst val) { BOOL res; uint32_t tag; tag = JS_VALUE_GET_NORM_TAG(val); switch(tag) { case JS_TAG_BIG_FLOAT: { JSBigFloat *p = JS_VALUE_GET_PTR(val); res = bf_is_finite(&p->num); } break; default: res = FALSE; break; } return res; } static JSValue js_bigfloat_toExponential(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; int64_t f; int rnd_mode, radix; val = js_thisBigFloatValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_ToInt64Sat(ctx, &f, argv[0])) goto fail; if (!js_bigfloat_is_finite(ctx, val)) { ret = JS_ToString(ctx, val); } else if (JS_IsUndefined(argv[0])) { ret = js_ftoa(ctx, val, 10, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN | BF_FTOA_FORCE_EXP); } else { if (f < 0 || f > BF_PREC_MAX) { JS_ThrowRangeError(ctx, "invalid number of digits"); goto fail; } rnd_mode = BF_RNDNA; radix = 10; if (argc > 1) { rnd_mode = bigfloat_get_rnd_mode(ctx, argv[1]); if (rnd_mode < 0) goto fail; } if (argc > 2) { radix = js_get_radix(ctx, argv[2]); if (radix < 0) goto fail; } ret = js_ftoa(ctx, val, radix, f + 1, rnd_mode | BF_FTOA_FORMAT_FIXED | BF_FTOA_FORCE_EXP); } JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_bigfloat_toPrecision(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; int64_t p; int rnd_mode, radix; val = js_thisBigFloatValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_IsUndefined(argv[0])) goto to_string; if (JS_ToInt64Sat(ctx, &p, argv[0])) goto fail; if (!js_bigfloat_is_finite(ctx, val)) { to_string: ret = JS_ToString(ctx, this_val); } else { if (p < 1 || p > BF_PREC_MAX) { JS_ThrowRangeError(ctx, "invalid number of digits"); goto fail; } rnd_mode = BF_RNDNA; radix = 10; if (argc > 1) { rnd_mode = bigfloat_get_rnd_mode(ctx, argv[1]); if (rnd_mode < 0) goto fail; } if (argc > 2) { radix = js_get_radix(ctx, argv[2]); if (radix < 0) goto fail; } ret = js_ftoa(ctx, val, radix, p, rnd_mode | BF_FTOA_FORMAT_FIXED); } JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static const JSCFunctionListEntry js_bigfloat_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_bigfloat_toString ), JS_CFUNC_DEF("valueOf", 0, js_bigfloat_valueOf ), JS_CFUNC_DEF("toPrecision", 1, js_bigfloat_toPrecision ), JS_CFUNC_DEF("toFixed", 1, js_bigfloat_toFixed ), JS_CFUNC_DEF("toExponential", 1, js_bigfloat_toExponential ), }; static JSValue js_bigfloat_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; if (argc == 0) { bf_t *r; val = JS_NewBigFloat(ctx); if (JS_IsException(val)) return val; r = JS_GetBigFloat(val); bf_set_zero(r, 0); } else { val = JS_DupValue(ctx, argv[0]); redo: switch(JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_BIG_FLOAT: break; case JS_TAG_FLOAT64: { bf_t *r; double d = JS_VALUE_GET_FLOAT64(val); val = JS_NewBigFloat(ctx); if (JS_IsException(val)) break; r = JS_GetBigFloat(val); if (bf_set_float64(r, d)) goto fail; } break; case JS_TAG_INT: { bf_t *r; int32_t v = JS_VALUE_GET_INT(val); val = JS_NewBigFloat(ctx); if (JS_IsException(val)) break; r = JS_GetBigFloat(val); if (bf_set_si(r, v)) goto fail; } break; case JS_TAG_BIG_INT: /* We keep the full precision of the integer */ { JSBigFloat *p = JS_VALUE_GET_PTR(val); val = JS_MKPTR(JS_TAG_BIG_FLOAT, p); } break; case JS_TAG_BIG_DECIMAL: val = JS_ToStringFree(ctx, val); if (JS_IsException(val)) break; goto redo; case JS_TAG_STRING: { const char *str, *p; size_t len; int err; 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) { bf_t *r; val = JS_NewBigFloat(ctx); if (JS_IsException(val)) break; r = JS_GetBigFloat(val); bf_set_zero(r, 0); err = 0; } else { val = js_atof(ctx, p, &p, 0, ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_FLOAT | ATOD_ACCEPT_PREFIX_AFTER_SIGN); if (JS_IsException(val)) { JS_FreeCString(ctx, str); return JS_EXCEPTION; } p += skip_spaces(p); err = ((p - str) != len); } JS_FreeCString(ctx, str); if (err) { JS_FreeValue(ctx, val); return JS_ThrowSyntaxError(ctx, "invalid bigfloat literal"); } } break; case JS_TAG_OBJECT: val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); if (JS_IsException(val)) break; goto redo; case JS_TAG_NULL: case JS_TAG_UNDEFINED: default: JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert to bigfloat"); } } return val; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_bigfloat_get_const(JSContext *ctx, JSValueConst this_val, int magic) { bf_t *r; JSValue val; val = JS_NewBigFloat(ctx); if (JS_IsException(val)) return val; r = JS_GetBigFloat(val); switch(magic) { case 0: /* PI */ bf_const_pi(r, ctx->fp_env.prec, ctx->fp_env.flags); break; case 1: /* LN2 */ bf_const_log2(r, ctx->fp_env.prec, ctx->fp_env.flags); break; case 2: /* MIN_VALUE */ case 3: /* MAX_VALUE */ { slimb_t e_range, e; e_range = (limb_t)1 << (bf_get_exp_bits(ctx->fp_env.flags) - 1); bf_set_ui(r, 1); if (magic == 2) { e = -e_range + 2; if (ctx->fp_env.flags & BF_FLAG_SUBNORMAL) e -= ctx->fp_env.prec - 1; bf_mul_2exp(r, e, ctx->fp_env.prec, ctx->fp_env.flags); } else { bf_mul_2exp(r, ctx->fp_env.prec, ctx->fp_env.prec, ctx->fp_env.flags); bf_add_si(r, r, -1, ctx->fp_env.prec, ctx->fp_env.flags); bf_mul_2exp(r, e_range - ctx->fp_env.prec, ctx->fp_env.prec, ctx->fp_env.flags); } } break; case 4: /* EPSILON */ bf_set_ui(r, 1); bf_mul_2exp(r, 1 - ctx->fp_env.prec, ctx->fp_env.prec, ctx->fp_env.flags); break; default: abort(); } return val; } static JSValue js_bigfloat_parseFloat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { bf_t *a; const char *str; JSValue ret; int radix; JSFloatEnv *fe; str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; if (JS_ToInt32(ctx, &radix, argv[1])) { fail: JS_FreeCString(ctx, str); return JS_EXCEPTION; } if (radix != 0 && (radix < 2 || radix > 36)) { JS_ThrowRangeError(ctx, "radix must be between 2 and 36"); goto fail; } fe = &ctx->fp_env; if (argc > 2) { fe = JS_GetOpaque2(ctx, argv[2], JS_CLASS_FLOAT_ENV); if (!fe) goto fail; } ret = JS_NewBigFloat(ctx); if (JS_IsException(ret)) goto done; a = JS_GetBigFloat(ret); /* XXX: use js_atof() */ bf_atof(a, str, NULL, radix, fe->prec, fe->flags); done: JS_FreeCString(ctx, str); return ret; } static JSValue js_bigfloat_isFinite(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst val = argv[0]; JSBigFloat *p; if (JS_VALUE_GET_NORM_TAG(val) != JS_TAG_BIG_FLOAT) return JS_FALSE; p = JS_VALUE_GET_PTR(val); return JS_NewBool(ctx, bf_is_finite(&p->num)); } static JSValue js_bigfloat_isNaN(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst val = argv[0]; JSBigFloat *p; if (JS_VALUE_GET_NORM_TAG(val) != JS_TAG_BIG_FLOAT) return JS_FALSE; p = JS_VALUE_GET_PTR(val); return JS_NewBool(ctx, bf_is_nan(&p->num)); } enum { MATH_OP_ABS, MATH_OP_FLOOR, MATH_OP_CEIL, MATH_OP_ROUND, MATH_OP_TRUNC, MATH_OP_SQRT, MATH_OP_FPROUND, MATH_OP_ACOS, MATH_OP_ASIN, MATH_OP_ATAN, MATH_OP_ATAN2, MATH_OP_COS, MATH_OP_EXP, MATH_OP_LOG, MATH_OP_POW, MATH_OP_SIN, MATH_OP_TAN, MATH_OP_FMOD, MATH_OP_REM, MATH_OP_SIGN, MATH_OP_ADD, MATH_OP_SUB, MATH_OP_MUL, MATH_OP_DIV, }; static JSValue js_bigfloat_fop(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { bf_t a_s, *a, *r; JSFloatEnv *fe; int rnd_mode; JSValue op1, res; op1 = JS_ToNumeric(ctx, argv[0]); if (JS_IsException(op1)) return op1; a = JS_ToBigFloat(ctx, &a_s, op1); fe = &ctx->fp_env; if (argc > 1) { fe = JS_GetOpaque2(ctx, argv[1], JS_CLASS_FLOAT_ENV); if (!fe) goto fail; } res = JS_NewBigFloat(ctx); if (JS_IsException(res)) { fail: if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, op1); return JS_EXCEPTION; } r = JS_GetBigFloat(res); switch (magic) { case MATH_OP_ABS: bf_set(r, a); r->sign = 0; break; case MATH_OP_FLOOR: rnd_mode = BF_RNDD; goto rint; case MATH_OP_CEIL: rnd_mode = BF_RNDU; goto rint; case MATH_OP_ROUND: rnd_mode = BF_RNDNA; goto rint; case MATH_OP_TRUNC: rnd_mode = BF_RNDZ; rint: bf_set(r, a); fe->status |= bf_rint(r, rnd_mode); break; case MATH_OP_SQRT: fe->status |= bf_sqrt(r, a, fe->prec, fe->flags); break; case MATH_OP_FPROUND: bf_set(r, a); fe->status |= bf_round(r, fe->prec, fe->flags); break; case MATH_OP_ACOS: fe->status |= bf_acos(r, a, fe->prec, fe->flags); break; case MATH_OP_ASIN: fe->status |= bf_asin(r, a, fe->prec, fe->flags); break; case MATH_OP_ATAN: fe->status |= bf_atan(r, a, fe->prec, fe->flags); break; case MATH_OP_COS: fe->status |= bf_cos(r, a, fe->prec, fe->flags); break; case MATH_OP_EXP: fe->status |= bf_exp(r, a, fe->prec, fe->flags); break; case MATH_OP_LOG: fe->status |= bf_log(r, a, fe->prec, fe->flags); break; case MATH_OP_SIN: fe->status |= bf_sin(r, a, fe->prec, fe->flags); break; case MATH_OP_TAN: fe->status |= bf_tan(r, a, fe->prec, fe->flags); break; case MATH_OP_SIGN: if (bf_is_nan(a) || bf_is_zero(a)) { bf_set(r, a); } else { bf_set_si(r, 1 - 2 * a->sign); } break; default: abort(); } if (a == &a_s) bf_delete(a); JS_FreeValue(ctx, op1); return res; } static JSValue js_bigfloat_fop2(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { bf_t a_s, *a, b_s, *b, r_s, *r = &r_s; JSFloatEnv *fe; JSValue op1, op2, res; op1 = JS_ToNumeric(ctx, argv[0]); if (JS_IsException(op1)) return op1; op2 = JS_ToNumeric(ctx, argv[1]); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); return op2; } a = JS_ToBigFloat(ctx, &a_s, op1); b = JS_ToBigFloat(ctx, &b_s, op2); fe = &ctx->fp_env; if (argc > 2) { fe = JS_GetOpaque2(ctx, argv[2], JS_CLASS_FLOAT_ENV); if (!fe) goto fail; } res = JS_NewBigFloat(ctx); if (JS_IsException(res)) { fail: if (a == &a_s) bf_delete(a); if (b == &b_s) bf_delete(b); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return JS_EXCEPTION; } r = JS_GetBigFloat(res); switch (magic) { case MATH_OP_ATAN2: fe->status |= bf_atan2(r, a, b, fe->prec, fe->flags); break; case MATH_OP_POW: fe->status |= bf_pow(r, a, b, fe->prec, fe->flags | BF_POW_JS_QUIRKS); break; case MATH_OP_FMOD: fe->status |= bf_rem(r, a, b, fe->prec, fe->flags, BF_RNDZ); break; case MATH_OP_REM: fe->status |= bf_rem(r, a, b, fe->prec, fe->flags, BF_RNDN); break; case MATH_OP_ADD: fe->status |= bf_add(r, a, b, fe->prec, fe->flags); break; case MATH_OP_SUB: fe->status |= bf_sub(r, a, b, fe->prec, fe->flags); break; case MATH_OP_MUL: fe->status |= bf_mul(r, a, b, fe->prec, fe->flags); break; case MATH_OP_DIV: fe->status |= bf_div(r, a, b, fe->prec, fe->flags); break; default: abort(); } if (a == &a_s) bf_delete(a); if (b == &b_s) bf_delete(b); JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return res; } static const JSCFunctionListEntry js_bigfloat_funcs[] = { JS_CGETSET_MAGIC_DEF("PI", js_bigfloat_get_const, NULL, 0 ), JS_CGETSET_MAGIC_DEF("LN2", js_bigfloat_get_const, NULL, 1 ), JS_CGETSET_MAGIC_DEF("MIN_VALUE", js_bigfloat_get_const, NULL, 2 ), JS_CGETSET_MAGIC_DEF("MAX_VALUE", js_bigfloat_get_const, NULL, 3 ), JS_CGETSET_MAGIC_DEF("EPSILON", js_bigfloat_get_const, NULL, 4 ), JS_CFUNC_DEF("parseFloat", 1, js_bigfloat_parseFloat ), JS_CFUNC_DEF("isFinite", 1, js_bigfloat_isFinite ), JS_CFUNC_DEF("isNaN", 1, js_bigfloat_isNaN ), JS_CFUNC_MAGIC_DEF("abs", 1, js_bigfloat_fop, MATH_OP_ABS ), JS_CFUNC_MAGIC_DEF("fpRound", 1, js_bigfloat_fop, MATH_OP_FPROUND ), JS_CFUNC_MAGIC_DEF("floor", 1, js_bigfloat_fop, MATH_OP_FLOOR ), JS_CFUNC_MAGIC_DEF("ceil", 1, js_bigfloat_fop, MATH_OP_CEIL ), JS_CFUNC_MAGIC_DEF("round", 1, js_bigfloat_fop, MATH_OP_ROUND ), JS_CFUNC_MAGIC_DEF("trunc", 1, js_bigfloat_fop, MATH_OP_TRUNC ), JS_CFUNC_MAGIC_DEF("sqrt", 1, js_bigfloat_fop, MATH_OP_SQRT ), JS_CFUNC_MAGIC_DEF("acos", 1, js_bigfloat_fop, MATH_OP_ACOS ), JS_CFUNC_MAGIC_DEF("asin", 1, js_bigfloat_fop, MATH_OP_ASIN ), JS_CFUNC_MAGIC_DEF("atan", 1, js_bigfloat_fop, MATH_OP_ATAN ), JS_CFUNC_MAGIC_DEF("atan2", 2, js_bigfloat_fop2, MATH_OP_ATAN2 ), JS_CFUNC_MAGIC_DEF("cos", 1, js_bigfloat_fop, MATH_OP_COS ), JS_CFUNC_MAGIC_DEF("exp", 1, js_bigfloat_fop, MATH_OP_EXP ), JS_CFUNC_MAGIC_DEF("log", 1, js_bigfloat_fop, MATH_OP_LOG ), JS_CFUNC_MAGIC_DEF("pow", 2, js_bigfloat_fop2, MATH_OP_POW ), JS_CFUNC_MAGIC_DEF("sin", 1, js_bigfloat_fop, MATH_OP_SIN ), JS_CFUNC_MAGIC_DEF("tan", 1, js_bigfloat_fop, MATH_OP_TAN ), JS_CFUNC_MAGIC_DEF("sign", 1, js_bigfloat_fop, MATH_OP_SIGN ), JS_CFUNC_MAGIC_DEF("add", 2, js_bigfloat_fop2, MATH_OP_ADD ), JS_CFUNC_MAGIC_DEF("sub", 2, js_bigfloat_fop2, MATH_OP_SUB ), JS_CFUNC_MAGIC_DEF("mul", 2, js_bigfloat_fop2, MATH_OP_MUL ), JS_CFUNC_MAGIC_DEF("div", 2, js_bigfloat_fop2, MATH_OP_DIV ), JS_CFUNC_MAGIC_DEF("fmod", 2, js_bigfloat_fop2, MATH_OP_FMOD ), JS_CFUNC_MAGIC_DEF("remainder", 2, js_bigfloat_fop2, MATH_OP_REM ), }; /* FloatEnv */ static JSValue js_float_env_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSValue obj; JSFloatEnv *fe; int64_t prec; int flags, rndmode; prec = ctx->fp_env.prec; flags = ctx->fp_env.flags; if (!JS_IsUndefined(argv[0])) { if (JS_ToInt64Sat(ctx, &prec, argv[0])) return JS_EXCEPTION; if (prec < BF_PREC_MIN || prec > BF_PREC_MAX) return JS_ThrowRangeError(ctx, "invalid precision"); flags = BF_RNDN; /* RNDN, max exponent size, no subnormal */ if (argc > 1 && !JS_IsUndefined(argv[1])) { if (JS_ToInt32Sat(ctx, &rndmode, argv[1])) return JS_EXCEPTION; if (rndmode < BF_RNDN || rndmode > BF_RNDF) return JS_ThrowRangeError(ctx, "invalid rounding mode"); flags = rndmode; } } obj = JS_NewObjectClass(ctx, JS_CLASS_FLOAT_ENV); if (JS_IsException(obj)) return JS_EXCEPTION; fe = js_malloc(ctx, sizeof(*fe)); if (!fe) return JS_EXCEPTION; fe->prec = prec; fe->flags = flags; fe->status = 0; JS_SetOpaque(obj, fe); return obj; } static void js_float_env_finalizer(JSRuntime *rt, JSValue val) { JSFloatEnv *fe = JS_GetOpaque(val, JS_CLASS_FLOAT_ENV); js_free_rt(rt, fe); } static JSValue js_float_env_get_prec(JSContext *ctx, JSValueConst this_val) { return JS_NewInt64(ctx, ctx->fp_env.prec); } static JSValue js_float_env_get_expBits(JSContext *ctx, JSValueConst this_val) { return JS_NewInt32(ctx, bf_get_exp_bits(ctx->fp_env.flags)); } static JSValue js_float_env_setPrec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst func; int exp_bits, flags, saved_flags; JSValue ret; limb_t saved_prec; int64_t prec; func = argv[0]; if (JS_ToInt64Sat(ctx, &prec, argv[1])) return JS_EXCEPTION; if (prec < BF_PREC_MIN || prec > BF_PREC_MAX) return JS_ThrowRangeError(ctx, "invalid precision"); exp_bits = BF_EXP_BITS_MAX; if (argc > 2 && !JS_IsUndefined(argv[2])) { if (JS_ToInt32Sat(ctx, &exp_bits, argv[2])) return JS_EXCEPTION; if (exp_bits < BF_EXP_BITS_MIN || exp_bits > BF_EXP_BITS_MAX) return JS_ThrowRangeError(ctx, "invalid number of exponent bits"); } flags = BF_RNDN | BF_FLAG_SUBNORMAL | bf_set_exp_bits(exp_bits); saved_prec = ctx->fp_env.prec; saved_flags = ctx->fp_env.flags; ctx->fp_env.prec = prec; ctx->fp_env.flags = flags; ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); /* always restore the floating point precision */ ctx->fp_env.prec = saved_prec; ctx->fp_env.flags = saved_flags; return ret; } #define FE_PREC (-1) #define FE_EXP (-2) #define FE_RNDMODE (-3) #define FE_SUBNORMAL (-4) static JSValue js_float_env_proto_get_status(JSContext *ctx, JSValueConst this_val, int magic) { JSFloatEnv *fe; fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV); if (!fe) return JS_EXCEPTION; switch(magic) { case FE_PREC: return JS_NewInt64(ctx, fe->prec); case FE_EXP: return JS_NewInt32(ctx, bf_get_exp_bits(fe->flags)); case FE_RNDMODE: return JS_NewInt32(ctx, fe->flags & BF_RND_MASK); case FE_SUBNORMAL: return JS_NewBool(ctx, (fe->flags & BF_FLAG_SUBNORMAL) != 0); default: return JS_NewBool(ctx, (fe->status & magic) != 0); } } static JSValue js_float_env_proto_set_status(JSContext *ctx, JSValueConst this_val, JSValueConst val, int magic) { JSFloatEnv *fe; int b; int64_t prec; fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV); if (!fe) return JS_EXCEPTION; switch(magic) { case FE_PREC: if (JS_ToInt64Sat(ctx, &prec, val)) return JS_EXCEPTION; if (prec < BF_PREC_MIN || prec > BF_PREC_MAX) return JS_ThrowRangeError(ctx, "invalid precision"); fe->prec = prec; break; case FE_EXP: if (JS_ToInt32Sat(ctx, &b, val)) return JS_EXCEPTION; if (b < BF_EXP_BITS_MIN || b > BF_EXP_BITS_MAX) return JS_ThrowRangeError(ctx, "invalid number of exponent bits"); fe->flags = (fe->flags & ~(BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT)) | bf_set_exp_bits(b); break; case FE_RNDMODE: b = bigfloat_get_rnd_mode(ctx, val); if (b < 0) return JS_EXCEPTION; fe->flags = (fe->flags & ~BF_RND_MASK) | b; break; case FE_SUBNORMAL: b = JS_ToBool(ctx, val); fe->flags = (fe->flags & ~BF_FLAG_SUBNORMAL) | (b ? BF_FLAG_SUBNORMAL: 0); break; default: b = JS_ToBool(ctx, val); fe->status = (fe->status & ~magic) & ((-b) & magic); break; } return JS_UNDEFINED; } static JSValue js_float_env_clearStatus(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSFloatEnv *fe = JS_GetOpaque2(ctx, this_val, JS_CLASS_FLOAT_ENV); if (!fe) return JS_EXCEPTION; fe->status = 0; return JS_UNDEFINED; } static const JSCFunctionListEntry js_float_env_funcs[] = { JS_CGETSET_DEF("prec", js_float_env_get_prec, NULL ), JS_CGETSET_DEF("expBits", js_float_env_get_expBits, NULL ), JS_CFUNC_DEF("setPrec", 2, js_float_env_setPrec ), JS_PROP_INT32_DEF("RNDN", BF_RNDN, 0 ), JS_PROP_INT32_DEF("RNDZ", BF_RNDZ, 0 ), JS_PROP_INT32_DEF("RNDU", BF_RNDU, 0 ), JS_PROP_INT32_DEF("RNDD", BF_RNDD, 0 ), JS_PROP_INT32_DEF("RNDNA", BF_RNDNA, 0 ), JS_PROP_INT32_DEF("RNDA", BF_RNDA, 0 ), JS_PROP_INT32_DEF("RNDF", BF_RNDF, 0 ), JS_PROP_INT32_DEF("precMin", BF_PREC_MIN, 0 ), JS_PROP_INT64_DEF("precMax", BF_PREC_MAX, 0 ), JS_PROP_INT32_DEF("expBitsMin", BF_EXP_BITS_MIN, 0 ), JS_PROP_INT32_DEF("expBitsMax", BF_EXP_BITS_MAX, 0 ), }; static const JSCFunctionListEntry js_float_env_proto_funcs[] = { JS_CGETSET_MAGIC_DEF("prec", js_float_env_proto_get_status, js_float_env_proto_set_status, FE_PREC ), JS_CGETSET_MAGIC_DEF("expBits", js_float_env_proto_get_status, js_float_env_proto_set_status, FE_EXP ), JS_CGETSET_MAGIC_DEF("rndMode", js_float_env_proto_get_status, js_float_env_proto_set_status, FE_RNDMODE ), JS_CGETSET_MAGIC_DEF("subnormal", js_float_env_proto_get_status, js_float_env_proto_set_status, FE_SUBNORMAL ), JS_CGETSET_MAGIC_DEF("invalidOperation", js_float_env_proto_get_status, js_float_env_proto_set_status, BF_ST_INVALID_OP ), JS_CGETSET_MAGIC_DEF("divideByZero", js_float_env_proto_get_status, js_float_env_proto_set_status, BF_ST_DIVIDE_ZERO ), JS_CGETSET_MAGIC_DEF("overflow", js_float_env_proto_get_status, js_float_env_proto_set_status, BF_ST_OVERFLOW ), JS_CGETSET_MAGIC_DEF("underflow", js_float_env_proto_get_status, js_float_env_proto_set_status, BF_ST_UNDERFLOW ), JS_CGETSET_MAGIC_DEF("inexact", js_float_env_proto_get_status, js_float_env_proto_set_status, BF_ST_INEXACT ), JS_CFUNC_DEF("clearStatus", 0, js_float_env_clearStatus ), }; void JS_AddIntrinsicBigFloat(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; JSValueConst obj2; rt->bigfloat_ops.to_string = js_bigfloat_to_string; rt->bigfloat_ops.from_string = js_string_to_bigfloat; rt->bigfloat_ops.unary_arith = js_unary_arith_bigfloat; rt->bigfloat_ops.binary_arith = js_binary_arith_bigfloat; rt->bigfloat_ops.compare = js_compare_bigfloat; rt->bigfloat_ops.mul_pow10_to_float64 = js_mul_pow10_to_float64; rt->bigfloat_ops.mul_pow10 = js_mul_pow10; ctx->class_proto[JS_CLASS_BIG_FLOAT] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_FLOAT], js_bigfloat_proto_funcs, countof(js_bigfloat_proto_funcs)); obj1 = JS_NewCFunction(ctx, js_bigfloat_constructor, "BigFloat", 1); JS_NewGlobalCConstructor2(ctx, obj1, "BigFloat", ctx->class_proto[JS_CLASS_BIG_FLOAT]); JS_SetPropertyFunctionList(ctx, obj1, js_bigfloat_funcs, countof(js_bigfloat_funcs)); ctx->class_proto[JS_CLASS_FLOAT_ENV] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_FLOAT_ENV], js_float_env_proto_funcs, countof(js_float_env_proto_funcs)); obj2 = JS_NewGlobalCConstructorOnly(ctx, "BigFloatEnv", js_float_env_constructor, 1, ctx->class_proto[JS_CLASS_FLOAT_ENV]); JS_SetPropertyFunctionList(ctx, obj2, js_float_env_funcs, countof(js_float_env_funcs)); } /* BigDecimal */ static JSValue JS_ToBigDecimalFree(JSContext *ctx, JSValue val, BOOL allow_null_or_undefined) { redo: switch(JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_BIG_DECIMAL: break; case JS_TAG_NULL: if (!allow_null_or_undefined) goto fail; /* fall thru */ case JS_TAG_BOOL: case JS_TAG_INT: { bfdec_t *r; int32_t v = JS_VALUE_GET_INT(val); val = JS_NewBigDecimal(ctx); if (JS_IsException(val)) break; r = JS_GetBigDecimal(val); if (bfdec_set_si(r, v)) { JS_FreeValue(ctx, val); val = JS_EXCEPTION; break; } } break; case JS_TAG_FLOAT64: case JS_TAG_BIG_INT: case JS_TAG_BIG_FLOAT: val = JS_ToStringFree(ctx, val); if (JS_IsException(val)) break; goto redo; case JS_TAG_STRING: { const char *str, *p; size_t len; int err; 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) { bfdec_t *r; val = JS_NewBigDecimal(ctx); if (JS_IsException(val)) break; r = JS_GetBigDecimal(val); bfdec_set_zero(r, 0); err = 0; } else { val = js_atof(ctx, p, &p, 0, ATOD_TYPE_BIG_DECIMAL); if (JS_IsException(val)) { JS_FreeCString(ctx, str); return JS_EXCEPTION; } p += skip_spaces(p); err = ((p - str) != len); } JS_FreeCString(ctx, str); if (err) { JS_FreeValue(ctx, val); return JS_ThrowSyntaxError(ctx, "invalid bigdecimal literal"); } } break; case JS_TAG_OBJECT: val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); if (JS_IsException(val)) break; goto redo; case JS_TAG_UNDEFINED: { bfdec_t *r; if (!allow_null_or_undefined) goto fail; val = JS_NewBigDecimal(ctx); if (JS_IsException(val)) break; r = JS_GetBigDecimal(val); bfdec_set_nan(r); } break; default: fail: JS_FreeValue(ctx, val); return JS_ThrowTypeError(ctx, "cannot convert to bigdecimal"); } return val; } static JSValue js_bigdecimal_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; if (argc == 0) { bfdec_t *r; val = JS_NewBigDecimal(ctx); if (JS_IsException(val)) return val; r = JS_GetBigDecimal(val); bfdec_set_zero(r, 0); } else { val = JS_ToBigDecimalFree(ctx, JS_DupValue(ctx, argv[0]), FALSE); } return val; } static JSValue js_thisBigDecimalValue(JSContext *ctx, JSValueConst this_val) { if (JS_IsBigDecimal(this_val)) 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_BIG_DECIMAL) { if (JS_IsBigDecimal(p->u.object_data)) return JS_DupValue(ctx, p->u.object_data); } } return JS_ThrowTypeError(ctx, "not a bigdecimal"); } static JSValue js_bigdecimal_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val; val = js_thisBigDecimalValue(ctx, this_val); if (JS_IsException(val)) return val; return JS_ToStringFree(ctx, val); } static JSValue js_bigdecimal_valueOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_thisBigDecimalValue(ctx, this_val); } static int js_bigdecimal_get_rnd_mode(JSContext *ctx, JSValueConst obj) { const char *str; size_t size; int rnd_mode; str = JS_ToCStringLen(ctx, &size, obj); if (!str) return -1; if (strlen(str) != size) goto invalid_rounding_mode; if (!strcmp(str, "floor")) { rnd_mode = BF_RNDD; } else if (!strcmp(str, "ceiling")) { rnd_mode = BF_RNDU; } else if (!strcmp(str, "down")) { rnd_mode = BF_RNDZ; } else if (!strcmp(str, "up")) { rnd_mode = BF_RNDA; } else if (!strcmp(str, "half-even")) { rnd_mode = BF_RNDN; } else if (!strcmp(str, "half-up")) { rnd_mode = BF_RNDNA; } else { invalid_rounding_mode: JS_FreeCString(ctx, str); JS_ThrowTypeError(ctx, "invalid rounding mode"); return -1; } JS_FreeCString(ctx, str); return rnd_mode; } typedef struct { int64_t prec; bf_flags_t flags; } BigDecimalEnv; static int js_bigdecimal_get_env(JSContext *ctx, BigDecimalEnv *fe, JSValueConst obj) { JSValue prop; int64_t val; BOOL has_prec; int rnd_mode; if (!JS_IsObject(obj)) { JS_ThrowTypeErrorNotAnObject(ctx); return -1; } prop = JS_GetProperty(ctx, obj, JS_ATOM_roundingMode); if (JS_IsException(prop)) return -1; rnd_mode = js_bigdecimal_get_rnd_mode(ctx, prop); JS_FreeValue(ctx, prop); if (rnd_mode < 0) return -1; fe->flags = rnd_mode; prop = JS_GetProperty(ctx, obj, JS_ATOM_maximumSignificantDigits); if (JS_IsException(prop)) return -1; has_prec = FALSE; if (!JS_IsUndefined(prop)) { if (JS_ToInt64SatFree(ctx, &val, prop)) return -1; if (val < 1 || val > BF_PREC_MAX) goto invalid_precision; fe->prec = val; has_prec = TRUE; } prop = JS_GetProperty(ctx, obj, JS_ATOM_maximumFractionDigits); if (JS_IsException(prop)) return -1; if (!JS_IsUndefined(prop)) { if (has_prec) { JS_FreeValue(ctx, prop); JS_ThrowTypeError(ctx, "cannot provide both maximumSignificantDigits and maximumFractionDigits"); return -1; } if (JS_ToInt64SatFree(ctx, &val, prop)) return -1; if (val < 0 || val > BF_PREC_MAX) { invalid_precision: JS_ThrowTypeError(ctx, "invalid precision"); return -1; } fe->prec = val; fe->flags |= BF_FLAG_RADPNT_PREC; has_prec = TRUE; } if (!has_prec) { JS_ThrowTypeError(ctx, "precision must be present"); return -1; } return 0; } static JSValue js_bigdecimal_fop(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { bfdec_t *a, *b, r_s, *r = &r_s; JSValue op1, op2, res; BigDecimalEnv fe_s, *fe = &fe_s; int op_count, ret; if (magic == MATH_OP_SQRT || magic == MATH_OP_ROUND) op_count = 1; else op_count = 2; op1 = JS_ToNumeric(ctx, argv[0]); if (JS_IsException(op1)) return op1; a = JS_ToBigDecimal(ctx, op1); if (!a) { JS_FreeValue(ctx, op1); return JS_EXCEPTION; } if (op_count >= 2) { op2 = JS_ToNumeric(ctx, argv[1]); if (JS_IsException(op2)) { JS_FreeValue(ctx, op1); return op2; } b = JS_ToBigDecimal(ctx, op2); if (!b) goto fail; } else { op2 = JS_UNDEFINED; b = NULL; } fe->flags = BF_RNDZ; fe->prec = BF_PREC_INF; if (op_count < argc) { if (js_bigdecimal_get_env(ctx, fe, argv[op_count])) goto fail; } res = JS_NewBigDecimal(ctx); if (JS_IsException(res)) { fail: JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); return JS_EXCEPTION; } r = JS_GetBigDecimal(res); switch (magic) { case MATH_OP_ADD: ret = bfdec_add(r, a, b, fe->prec, fe->flags); break; case MATH_OP_SUB: ret = bfdec_sub(r, a, b, fe->prec, fe->flags); break; case MATH_OP_MUL: ret = bfdec_mul(r, a, b, fe->prec, fe->flags); break; case MATH_OP_DIV: ret = bfdec_div(r, a, b, fe->prec, fe->flags); break; case MATH_OP_FMOD: ret = bfdec_rem(r, a, b, fe->prec, fe->flags, BF_RNDZ); break; case MATH_OP_SQRT: ret = bfdec_sqrt(r, a, fe->prec, fe->flags); break; case MATH_OP_ROUND: ret = bfdec_set(r, a); if (!(ret & BF_ST_MEM_ERROR)) ret = bfdec_round(r, fe->prec, fe->flags); break; default: abort(); } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); ret &= BF_ST_MEM_ERROR | BF_ST_DIVIDE_ZERO | BF_ST_INVALID_OP | BF_ST_OVERFLOW; if (ret != 0) { JS_FreeValue(ctx, res); return throw_bf_exception(ctx, ret); } else { return res; } } static JSValue js_bigdecimal_toFixed(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; int64_t f; int rnd_mode; val = js_thisBigDecimalValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_ToInt64Sat(ctx, &f, argv[0])) goto fail; if (f < 0 || f > BF_PREC_MAX) { JS_ThrowRangeError(ctx, "invalid number of digits"); goto fail; } rnd_mode = BF_RNDNA; if (argc > 1) { rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv[1]); if (rnd_mode < 0) goto fail; } ret = js_bigdecimal_to_string1(ctx, val, f, rnd_mode | BF_FTOA_FORMAT_FRAC); JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_bigdecimal_toExponential(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; int64_t f; int rnd_mode; val = js_thisBigDecimalValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_ToInt64Sat(ctx, &f, argv[0])) goto fail; if (JS_IsUndefined(argv[0])) { ret = js_bigdecimal_to_string1(ctx, val, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN | BF_FTOA_FORCE_EXP); } else { if (f < 0 || f > BF_PREC_MAX) { JS_ThrowRangeError(ctx, "invalid number of digits"); goto fail; } rnd_mode = BF_RNDNA; if (argc > 1) { rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv[1]); if (rnd_mode < 0) goto fail; } ret = js_bigdecimal_to_string1(ctx, val, f + 1, rnd_mode | BF_FTOA_FORMAT_FIXED | BF_FTOA_FORCE_EXP); } JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static JSValue js_bigdecimal_toPrecision(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue val, ret; int64_t p; int rnd_mode; val = js_thisBigDecimalValue(ctx, this_val); if (JS_IsException(val)) return val; if (JS_IsUndefined(argv[0])) { return JS_ToStringFree(ctx, val); } if (JS_ToInt64Sat(ctx, &p, argv[0])) goto fail; if (p < 1 || p > BF_PREC_MAX) { JS_ThrowRangeError(ctx, "invalid number of digits"); goto fail; } rnd_mode = BF_RNDNA; if (argc > 1) { rnd_mode = js_bigdecimal_get_rnd_mode(ctx, argv[1]); if (rnd_mode < 0) goto fail; } ret = js_bigdecimal_to_string1(ctx, val, p, rnd_mode | BF_FTOA_FORMAT_FIXED); JS_FreeValue(ctx, val); return ret; fail: JS_FreeValue(ctx, val); return JS_EXCEPTION; } static const JSCFunctionListEntry js_bigdecimal_proto_funcs[] = { JS_CFUNC_DEF("toString", 0, js_bigdecimal_toString ), JS_CFUNC_DEF("valueOf", 0, js_bigdecimal_valueOf ), JS_CFUNC_DEF("toPrecision", 1, js_bigdecimal_toPrecision ), JS_CFUNC_DEF("toFixed", 1, js_bigdecimal_toFixed ), JS_CFUNC_DEF("toExponential", 1, js_bigdecimal_toExponential ), }; static const JSCFunctionListEntry js_bigdecimal_funcs[] = { JS_CFUNC_MAGIC_DEF("add", 2, js_bigdecimal_fop, MATH_OP_ADD ), JS_CFUNC_MAGIC_DEF("sub", 2, js_bigdecimal_fop, MATH_OP_SUB ), JS_CFUNC_MAGIC_DEF("mul", 2, js_bigdecimal_fop, MATH_OP_MUL ), JS_CFUNC_MAGIC_DEF("div", 2, js_bigdecimal_fop, MATH_OP_DIV ), JS_CFUNC_MAGIC_DEF("mod", 2, js_bigdecimal_fop, MATH_OP_FMOD ), JS_CFUNC_MAGIC_DEF("round", 1, js_bigdecimal_fop, MATH_OP_ROUND ), JS_CFUNC_MAGIC_DEF("sqrt", 1, js_bigdecimal_fop, MATH_OP_SQRT ), }; void JS_AddIntrinsicBigDecimal(JSContext *ctx) { JSRuntime *rt = ctx->rt; JSValue obj1; rt->bigdecimal_ops.to_string = js_bigdecimal_to_string; rt->bigdecimal_ops.from_string = js_string_to_bigdecimal; rt->bigdecimal_ops.unary_arith = js_unary_arith_bigdecimal; rt->bigdecimal_ops.binary_arith = js_binary_arith_bigdecimal; rt->bigdecimal_ops.compare = js_compare_bigdecimal; ctx->class_proto[JS_CLASS_BIG_DECIMAL] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_DECIMAL], js_bigdecimal_proto_funcs, countof(js_bigdecimal_proto_funcs)); obj1 = JS_NewCFunction(ctx, js_bigdecimal_constructor, "BigDecimal", 1); JS_NewGlobalCConstructor2(ctx, obj1, "BigDecimal", ctx->class_proto[JS_CLASS_BIG_DECIMAL]); JS_SetPropertyFunctionList(ctx, obj1, js_bigdecimal_funcs, countof(js_bigdecimal_funcs)); } void JS_EnableBignumExt(JSContext *ctx, BOOL enable) { ctx->bignum_ext = enable; } #endif /* CONFIG_BIGNUM */ static const char * const native_error_name[JS_NATIVE_ERROR_COUNT] = { "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "InternalError", }; /* 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); 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); #if 0 /* these are auto-initialized from js_error_proto_funcs, but delaying might be a problem */ JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ERROR], JS_ATOM_name, JS_AtomToString(ctx, JS_ATOM_Error), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ERROR], JS_ATOM_message, JS_AtomToString(ctx, JS_ATOM_empty_string), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); #endif 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 and arguments properties to throw a TypeError */ obj1 = JS_NewCFunction(ctx, js_function_proto_caller, NULL, 0); JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_caller, JS_UNDEFINED, obj1, ctx->throw_type_error, JS_PROP_HAS_GET | JS_PROP_HAS_SET | JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE); JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_arguments, JS_UNDEFINED, obj1, ctx->throw_type_error, JS_PROP_HAS_GET | JS_PROP_HAS_SET | JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE); JS_FreeValue(ctx, obj1); JS_FreeValue(ctx, js_object_seal(ctx, JS_UNDEFINED, 1, (JSValueConst *)&ctx->throw_type_error, 1)); ctx->global_obj = JS_NewObject(ctx); ctx->global_var_obj = JS_NewObjectProto(ctx, JS_NULL); /* Object */ obj = JS_NewGlobalCConstructor(ctx, "Object", js_object_constructor, 1, ctx->class_proto[JS_CLASS_OBJECT]); JS_SetPropertyFunctionList(ctx, obj, js_object_funcs, countof(js_object_funcs)); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_OBJECT], js_object_proto_funcs, countof(js_object_proto_funcs)); /* Function */ JS_SetPropertyFunctionList(ctx, ctx->function_proto, js_function_proto_funcs, countof(js_function_proto_funcs)); ctx->function_ctor = JS_NewCFunctionMagic(ctx, js_function_constructor, "Function", 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_NORMAL); JS_NewGlobalCConstructor2(ctx, JS_DupValue(ctx, ctx->function_ctor), "Function", ctx->function_proto); /* 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]); for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { JSValue func_obj = JS_NewCFunction3(ctx, (JSCFunction *)js_error_constructor, native_error_name[i], 1, JS_CFUNC_constructor_or_func_magic, i, obj1); JS_NewGlobalCConstructor2(ctx, func_obj, native_error_name[i], ctx->native_error_proto[i]); } /* Iterator prototype */ ctx->iterator_proto = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->iterator_proto, js_iterator_proto_funcs, countof(js_iterator_proto_funcs)); /* 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]); JS_SetPropertyFunctionList(ctx, obj, js_array_funcs, countof(js_array_funcs)); /* XXX: create auto_initializer */ { /* initialize Array.prototype[Symbol.unscopables] */ char const unscopables[] = "copyWithin" "\0" "entries" "\0" "fill" "\0" "find" "\0" "findIndex" "\0" "flat" "\0" "flatMap" "\0" "includes" "\0" "keys" "\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); } /* needed to initialize arguments[Symbol.iterator] */ ctx->array_proto_values = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_values); ctx->class_proto[JS_CLASS_ARRAY_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_ITERATOR], js_array_iterator_proto_funcs, countof(js_array_iterator_proto_funcs)); /* Number */ ctx->class_proto[JS_CLASS_NUMBER] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_NUMBER); JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_NUMBER], JS_NewInt32(ctx, 0)); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_NUMBER], js_number_proto_funcs, countof(js_number_proto_funcs)); number_obj = JS_NewGlobalCConstructor(ctx, "Number", js_number_constructor, 1, ctx->class_proto[JS_CLASS_NUMBER]); JS_SetPropertyFunctionList(ctx, number_obj, js_number_funcs, countof(js_number_funcs)); /* Boolean */ ctx->class_proto[JS_CLASS_BOOLEAN] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_BOOLEAN); JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], JS_NewBool(ctx, FALSE)); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], js_boolean_proto_funcs, countof(js_boolean_proto_funcs)); JS_NewGlobalCConstructor(ctx, "Boolean", js_boolean_constructor, 1, ctx->class_proto[JS_CLASS_BOOLEAN]); /* String */ ctx->class_proto[JS_CLASS_STRING] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_STRING); JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_STRING], JS_AtomToString(ctx, JS_ATOM_empty_string)); obj = JS_NewGlobalCConstructor(ctx, "String", js_string_constructor, 1, ctx->class_proto[JS_CLASS_STRING]); JS_SetPropertyFunctionList(ctx, obj, js_string_funcs, countof(js_string_funcs)); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_funcs, countof(js_string_proto_funcs)); ctx->class_proto[JS_CLASS_STRING_ITERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING_ITERATOR], js_string_iterator_proto_funcs, countof(js_string_iterator_proto_funcs)); /* Math: create as autoinit object */ js_random_init(ctx); JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj, countof(js_math_obj)); /* ES6 Reflect: create as autoinit object */ JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_reflect_obj, countof(js_reflect_obj)); /* ES6 Symbol */ ctx->class_proto[JS_CLASS_SYMBOL] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SYMBOL], js_symbol_proto_funcs, countof(js_symbol_proto_funcs)); obj = JS_NewGlobalCConstructor(ctx, "Symbol", js_symbol_constructor, 0, ctx->class_proto[JS_CLASS_SYMBOL]); JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs, countof(js_symbol_funcs)); for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) { char buf[ATOM_GET_STR_BUF_SIZE]; const char *str, *p; str = JS_AtomGetStr(ctx, buf, sizeof(buf), i); /* skip "Symbol." */ p = strchr(str, '.'); if (p) str = p + 1; JS_DefinePropertyValueStr(ctx, obj, str, JS_AtomToValue(ctx, i), 0); } /* ES6 Generator */ ctx->class_proto[JS_CLASS_GENERATOR] = JS_NewObjectProto(ctx, ctx->iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_GENERATOR], js_generator_proto_funcs, countof(js_generator_proto_funcs)); ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto); obj1 = JS_NewCFunctionMagic(ctx, js_function_constructor, "GeneratorFunction", 1, JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION], js_generator_function_proto_funcs, countof(js_generator_function_proto_funcs)); JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION], ctx->class_proto[JS_CLASS_GENERATOR], JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE); JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION], 0, JS_PROP_CONFIGURABLE); JS_FreeValue(ctx, obj1); /* global properties */ ctx->eval_obj = JS_NewCFunction(ctx, js_global_eval, "eval", 1); JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_eval, JS_DupValue(ctx, ctx->eval_obj), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs, countof(js_global_funcs)); JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis, JS_DupValue(ctx, ctx->global_obj), JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); } /* Typed Arrays */ static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = { 0, 0, 0, 1, 1, 2, 2, #ifdef CONFIG_BIGNUM 3, 3, /* BigInt64Array, BigUint64Array */ #endif 2, 3 }; static JSValue js_array_buffer_constructor3(JSContext *ctx, JSValueConst new_target, uint64_t len, JSClassID class_id, uint8_t *buf, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL alloc_flag) { JSValue obj; JSArrayBuffer *abuf = NULL; obj = js_create_from_ctor(ctx, new_target, class_id); if (JS_IsException(obj)) return obj; /* XXX: we are currently limited to 2 GB */ if (len > INT32_MAX) { JS_ThrowRangeError(ctx, "invalid array buffer length"); goto fail; } abuf = js_malloc(ctx, sizeof(*abuf)); if (!abuf) goto fail; abuf->byte_length = len; if (alloc_flag) { /* the allocation must be done after the object creation */ abuf->data = js_mallocz(ctx, max_int(len, 1)); if (!abuf->data) goto fail; } else { abuf->data = buf; } init_list_head(&abuf->array_list); abuf->detached = FALSE; abuf->shared = (class_id == JS_CLASS_SHARED_ARRAY_BUFFER); abuf->opaque = opaque; abuf->free_func = free_func; if (alloc_flag && buf) memcpy(abuf->data, buf, len); JS_SetOpaque(obj, abuf); return obj; fail: JS_FreeValue(ctx, obj); js_free(ctx, abuf); return JS_EXCEPTION; } static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr) { js_free_rt(rt, ptr); } static JSValue js_array_buffer_constructor2(JSContext *ctx, JSValueConst new_target, uint64_t len, JSClassID class_id) { return js_array_buffer_constructor3(ctx, new_target, len, class_id, NULL, js_array_buffer_free, NULL, TRUE); } static JSValue js_array_buffer_constructor1(JSContext *ctx, JSValueConst new_target, uint64_t len) { return js_array_buffer_constructor2(ctx, new_target, len, JS_CLASS_ARRAY_BUFFER); } JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL is_shared) { return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER, buf, free_func, opaque, FALSE); } /* create a new ArrayBuffer of length 'len' and copy 'buf' to it */ JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len) { return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, JS_CLASS_ARRAY_BUFFER, (uint8_t *)buf, js_array_buffer_free, NULL, TRUE); } static JSValue js_array_buffer_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { uint64_t len; if (JS_ToIndex(ctx, &len, argv[0])) return JS_EXCEPTION; return js_array_buffer_constructor1(ctx, new_target, len); } static JSValue js_shared_array_buffer_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { uint64_t len; if (JS_ToIndex(ctx, &len, argv[0])) return JS_EXCEPTION; return js_array_buffer_constructor2(ctx, new_target, len, JS_CLASS_SHARED_ARRAY_BUFFER); } /* also used for SharedArrayBuffer */ static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSArrayBuffer *abuf = p->u.array_buffer; if (abuf) { /* The ArrayBuffer finalizer may be called before the typed array finalizers using it, so abuf->array_list is not necessarily empty. */ // assert(list_empty(&abuf->array_list)); if (abuf->free_func) abuf->free_func(rt, abuf->opaque, abuf->data); js_free_rt(rt, abuf); } } static JSValue js_array_buffer_isView(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSObject *p; BOOL res; res = FALSE; if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ(argv[0]); if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_DATAVIEW) { res = TRUE; } } return JS_NewBool(ctx, res); } static const JSCFunctionListEntry js_array_buffer_funcs[] = { JS_CFUNC_DEF("isView", 1, js_array_buffer_isView ), JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), }; static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx) { return JS_ThrowTypeError(ctx, "ArrayBuffer is detached"); } static JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValueConst this_val, int class_id) { JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); if (!abuf) return JS_EXCEPTION; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return JS_NewUint32(ctx, abuf->byte_length); } void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj) { JSArrayBuffer *abuf = JS_GetOpaque(obj, JS_CLASS_ARRAY_BUFFER); struct list_head *el; if (!abuf || abuf->detached) return; if (abuf->free_func) abuf->free_func(ctx->rt, abuf->opaque, abuf->data); abuf->data = NULL; abuf->byte_length = 0; abuf->detached = TRUE; list_for_each(el, &abuf->array_list) { JSTypedArray *ta; JSObject *p; ta = list_entry(el, JSTypedArray, link); p = ta->obj; /* Note: the typed array length and offset fields are not modified */ if (p->class_id != JS_CLASS_DATAVIEW) { p->u.array.count = 0; p->u.array.u.ptr = NULL; } } } /* get an ArrayBuffer or SharedArrayBuffer */ static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj) { JSObject *p; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) goto fail; p = JS_VALUE_GET_OBJ(obj); if (p->class_id != JS_CLASS_ARRAY_BUFFER && p->class_id != JS_CLASS_SHARED_ARRAY_BUFFER) { fail: JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_ARRAY_BUFFER); return NULL; } return p->u.array_buffer; } /* return NULL if exception. WARNING: any JS call can detach the buffer and render the returned pointer invalid */ uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj) { JSArrayBuffer *abuf = js_get_array_buffer(ctx, obj); if (!abuf) goto fail; if (abuf->detached) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } *psize = abuf->byte_length; return abuf->data; fail: *psize = 0; return NULL; } static JSValue js_array_buffer_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int class_id) { JSArrayBuffer *abuf, *new_abuf; int64_t len, start, end, new_len; JSValue ctor, new_obj; abuf = JS_GetOpaque2(ctx, this_val, class_id); if (!abuf) return JS_EXCEPTION; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); len = abuf->byte_length; if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) return JS_EXCEPTION; end = len; if (!JS_IsUndefined(argv[1])) { if (JS_ToInt64Clamp(ctx, &end, argv[1], 0, len, len)) return JS_EXCEPTION; } new_len = max_int64(end - start, 0); ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED); if (JS_IsException(ctor)) return ctor; if (JS_IsUndefined(ctor)) { new_obj = js_array_buffer_constructor2(ctx, JS_UNDEFINED, new_len, class_id); } else { JSValue args[1]; args[0] = JS_NewInt64(ctx, new_len); new_obj = JS_CallConstructor(ctx, ctor, 1, (JSValueConst *)args); JS_FreeValue(ctx, ctor); JS_FreeValue(ctx, args[0]); } if (JS_IsException(new_obj)) return new_obj; new_abuf = JS_GetOpaque2(ctx, new_obj, class_id); if (!new_abuf) goto fail; if (js_same_value(ctx, new_obj, this_val)) { JS_ThrowTypeError(ctx, "cannot use identical ArrayBuffer"); goto fail; } if (new_abuf->detached) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } if (new_abuf->byte_length < new_len) { JS_ThrowTypeError(ctx, "new ArrayBuffer is too small"); goto fail; } /* must test again because of side effects */ if (abuf->detached) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } memcpy(new_abuf->data, abuf->data + start, new_len); return new_obj; fail: JS_FreeValue(ctx, new_obj); return JS_EXCEPTION; } static const JSCFunctionListEntry js_array_buffer_proto_funcs[] = { JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_ARRAY_BUFFER ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_ARRAY_BUFFER ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "ArrayBuffer", JS_PROP_CONFIGURABLE ), }; /* SharedArrayBuffer */ static const JSCFunctionListEntry js_shared_array_buffer_funcs[] = { JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), }; static const JSCFunctionListEntry js_shared_array_buffer_proto_funcs[] = { JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_SHARED_ARRAY_BUFFER ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "SharedArrayBuffer", JS_PROP_CONFIGURABLE ), }; static JSObject *get_typed_array(JSContext *ctx, JSValueConst this_val, int is_dataview) { JSObject *p; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) goto fail; p = JS_VALUE_GET_OBJ(this_val); if (is_dataview) { if (p->class_id != JS_CLASS_DATAVIEW) goto fail; } else { if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY)) { fail: JS_ThrowTypeError(ctx, "not a %s", is_dataview ? "DataView" : "TypedArray"); return NULL; } } return p; } /* WARNING: 'p' must be a typed array */ static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p) { JSTypedArray *ta = p->u.typed_array; JSArrayBuffer *abuf = ta->buffer->u.array_buffer; /* XXX: could simplify test by ensuring that p->u.array.u.ptr is NULL iff it is detached */ return abuf->detached; } /* WARNING: 'p' must be a typed array. Works even if the array buffer is detached */ static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p) { JSTypedArray *ta = p->u.typed_array; int size_log2 = typed_array_size_log2(p->class_id); return ta->length >> size_log2; } static int validate_typed_array(JSContext *ctx, JSValueConst this_val) { JSObject *p; p = get_typed_array(ctx, this_val, 0); if (!p) return -1; if (typed_array_is_detached(ctx, p)) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return -1; } return 0; } static JSValue js_typed_array_get_length(JSContext *ctx, JSValueConst this_val) { JSObject *p; p = get_typed_array(ctx, this_val, 0); if (!p) return JS_EXCEPTION; return JS_NewInt32(ctx, p->u.array.count); } static JSValue js_typed_array_get_buffer(JSContext *ctx, JSValueConst this_val, int is_dataview) { JSObject *p; JSTypedArray *ta; p = get_typed_array(ctx, this_val, is_dataview); if (!p) return JS_EXCEPTION; ta = p->u.typed_array; return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); } static JSValue js_typed_array_get_byteLength(JSContext *ctx, JSValueConst this_val, int is_dataview) { JSObject *p; JSTypedArray *ta; p = get_typed_array(ctx, this_val, is_dataview); if (!p) return JS_EXCEPTION; if (typed_array_is_detached(ctx, p)) { if (is_dataview) { return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); } else { return JS_NewInt32(ctx, 0); } } ta = p->u.typed_array; return JS_NewInt32(ctx, ta->length); } static JSValue js_typed_array_get_byteOffset(JSContext *ctx, JSValueConst this_val, int is_dataview) { JSObject *p; JSTypedArray *ta; p = get_typed_array(ctx, this_val, is_dataview); if (!p) return JS_EXCEPTION; if (typed_array_is_detached(ctx, p)) { if (is_dataview) { return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); } else { return JS_NewInt32(ctx, 0); } } ta = p->u.typed_array; return JS_NewInt32(ctx, ta->offset); } /* Return the buffer associated to the typed array or an exception if it is not a typed array or if the buffer is detached. pbyte_offset, pbyte_length or pbytes_per_element can be NULL. */ JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValueConst obj, size_t *pbyte_offset, size_t *pbyte_length, size_t *pbytes_per_element) { JSObject *p; JSTypedArray *ta; p = get_typed_array(ctx, obj, FALSE); if (!p) return JS_EXCEPTION; if (typed_array_is_detached(ctx, p)) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); ta = p->u.typed_array; if (pbyte_offset) *pbyte_offset = ta->offset; if (pbyte_length) *pbyte_length = ta->length; if (pbytes_per_element) { *pbytes_per_element = 1 << typed_array_size_log2(p->class_id); } return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); } static JSValue js_typed_array_get_toStringTag(JSContext *ctx, JSValueConst this_val) { JSObject *p; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) return JS_UNDEFINED; p = JS_VALUE_GET_OBJ(this_val); if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY)) return JS_UNDEFINED; return JS_AtomToString(ctx, ctx->rt->class_array[p->class_id].class_name); } static JSValue js_typed_array_set_internal(JSContext *ctx, JSValueConst dst, JSValueConst src, JSValueConst off) { JSObject *p; JSObject *src_p; uint32_t i; int64_t src_len, offset; JSValue val, src_obj = JS_UNDEFINED; p = get_typed_array(ctx, dst, 0); if (!p) goto fail; if (JS_ToInt64Sat(ctx, &offset, off)) goto fail; if (offset < 0) goto range_error; if (typed_array_is_detached(ctx, p)) { detached: JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } src_obj = JS_ToObject(ctx, src); if (JS_IsException(src_obj)) goto fail; src_p = JS_VALUE_GET_OBJ(src_obj); if (src_p->class_id >= JS_CLASS_UINT8C_ARRAY && src_p->class_id <= JS_CLASS_FLOAT64_ARRAY) { JSTypedArray *dest_ta = p->u.typed_array; JSArrayBuffer *dest_abuf = dest_ta->buffer->u.array_buffer; JSTypedArray *src_ta = src_p->u.typed_array; JSArrayBuffer *src_abuf = src_ta->buffer->u.array_buffer; int shift = typed_array_size_log2(p->class_id); if (src_abuf->detached) goto detached; src_len = src_p->u.array.count; if (offset > (int64_t)(p->u.array.count - src_len)) goto range_error; /* copying between typed objects */ if (src_p->class_id == p->class_id) { /* same type, use memmove */ memmove(dest_abuf->data + dest_ta->offset + (offset << shift), src_abuf->data + src_ta->offset, src_len << shift); goto done; } if (dest_abuf->data == src_abuf->data) { /* copying between the same buffer using different types of mappings would require a temporary buffer */ } /* otherwise, default behavior is slow but correct */ } else { if (js_get_length64(ctx, &src_len, src_obj)) goto fail; if (offset > (int64_t)(p->u.array.count - src_len)) { range_error: JS_ThrowRangeError(ctx, "invalid array length"); goto fail; } } for(i = 0; i < src_len; i++) { val = JS_GetPropertyUint32(ctx, src_obj, i); if (JS_IsException(val)) goto fail; if (JS_SetPropertyUint32(ctx, dst, offset + i, val) < 0) goto fail; } done: JS_FreeValue(ctx, src_obj); return JS_UNDEFINED; fail: JS_FreeValue(ctx, src_obj); return JS_EXCEPTION; } static JSValue js_typed_array_set(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst offset = JS_UNDEFINED; if (argc > 1) { offset = argv[1]; } return js_typed_array_set_internal(ctx, this_val, argv[0], offset); } static JSValue js_create_typed_array_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { if (validate_typed_array(ctx, this_val)) return JS_EXCEPTION; return js_create_array_iterator(ctx, this_val, argc, argv, magic); } /* return < 0 if exception */ static int js_typed_array_get_length_internal(JSContext *ctx, JSValueConst obj) { JSObject *p; p = get_typed_array(ctx, obj, 0); if (!p) return -1; if (typed_array_is_detached(ctx, p)) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return -1; } return p->u.array.count; } #if 0 /* validate a typed array and return its length */ static JSValue js_typed_array___getLength(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { BOOL ignore_detached = JS_ToBool(ctx, argv[1]); if (ignore_detached) { return js_typed_array_get_length(ctx, argv[0]); } else { int len; len = js_typed_array_get_length_internal(ctx, argv[0]); if (len < 0) return JS_EXCEPTION; return JS_NewInt32(ctx, len); } } #endif static JSValue js_typed_array_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int classid); static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor, int argc, JSValueConst *argv) { JSValue ret; int new_len; int64_t len; ret = JS_CallConstructor(ctx, ctor, argc, argv); if (JS_IsException(ret)) return ret; /* validate the typed array */ new_len = js_typed_array_get_length_internal(ctx, ret); if (new_len < 0) goto fail; if (argc == 1) { /* ensure that it is large enough */ if (JS_ToLengthFree(ctx, &len, JS_DupValue(ctx, argv[0]))) goto fail; if (new_len < len) { JS_ThrowTypeError(ctx, "TypedArray length is too small"); fail: JS_FreeValue(ctx, ret); return JS_EXCEPTION; } } return ret; } #if 0 static JSValue js_typed_array___create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return js_typed_array_create(ctx, argv[0], max_int(argc - 1, 0), argv + 1); } #endif static JSValue js_typed_array___speciesCreate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst obj; JSObject *p; JSValue ctor, ret; int argc1; obj = argv[0]; p = get_typed_array(ctx, obj, 0); if (!p) return JS_EXCEPTION; ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED); if (JS_IsException(ctor)) return ctor; argc1 = max_int(argc - 1, 0); if (JS_IsUndefined(ctor)) { ret = js_typed_array_constructor(ctx, JS_UNDEFINED, argc1, argv + 1, p->class_id); } else { ret = js_typed_array_create(ctx, ctor, argc1, argv + 1); JS_FreeValue(ctx, ctor); } return ret; } static JSValue js_typed_array_from(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // from(items, mapfn = void 0, this_arg = void 0) JSValueConst items = argv[0], mapfn, this_arg; JSValueConst args[2]; JSValue stack[2]; JSValue iter, arr, r, v, v2; int64_t k, len; int done, mapping; mapping = FALSE; mapfn = JS_UNDEFINED; this_arg = JS_UNDEFINED; r = JS_UNDEFINED; arr = JS_UNDEFINED; stack[0] = JS_UNDEFINED; stack[1] = JS_UNDEFINED; if (argc > 1) { mapfn = argv[1]; if (!JS_IsUndefined(mapfn)) { if (check_function(ctx, mapfn)) goto exception; mapping = 1; if (argc > 2) this_arg = argv[2]; } } iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator); if (JS_IsException(iter)) goto exception; if (!JS_IsUndefined(iter)) { JS_FreeValue(ctx, iter); arr = JS_NewArray(ctx); if (JS_IsException(arr)) goto exception; stack[0] = JS_DupValue(ctx, items); if (js_for_of_start(ctx, &stack[1], FALSE)) goto exception; for (k = 0;; k++) { v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done); if (JS_IsException(v)) goto exception_close; if (done) break; if (JS_DefinePropertyValueInt64(ctx, arr, k, v, JS_PROP_C_W_E | JS_PROP_THROW) < 0) goto exception_close; } } else { arr = JS_ToObject(ctx, items); if (JS_IsException(arr)) goto exception; } if (js_get_length64(ctx, &len, arr) < 0) goto exception; v = JS_NewInt64(ctx, len); args[0] = v; r = js_typed_array_create(ctx, this_val, 1, args); JS_FreeValue(ctx, v); if (JS_IsException(r)) goto exception; for(k = 0; k < len; k++) { v = JS_GetPropertyInt64(ctx, arr, k); if (JS_IsException(v)) goto exception; if (mapping) { args[0] = v; args[1] = JS_NewInt32(ctx, k); v2 = JS_Call(ctx, mapfn, this_arg, 2, args); JS_FreeValue(ctx, v); v = v2; if (JS_IsException(v)) goto exception; } if (JS_SetPropertyInt64(ctx, r, k, v) < 0) goto exception; } goto done; exception_close: if (!JS_IsUndefined(stack[0])) JS_IteratorClose(ctx, stack[0], TRUE); exception: JS_FreeValue(ctx, r); r = JS_EXCEPTION; done: JS_FreeValue(ctx, arr); JS_FreeValue(ctx, stack[0]); JS_FreeValue(ctx, stack[1]); return r; } static JSValue js_typed_array_of(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue obj; JSValueConst args[1]; int i; args[0] = JS_NewInt32(ctx, argc); obj = js_typed_array_create(ctx, this_val, 1, args); if (JS_IsException(obj)) return obj; for(i = 0; i < argc; i++) { if (JS_SetPropertyUint32(ctx, obj, i, JS_DupValue(ctx, argv[i])) < 0) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } } return obj; } static JSValue js_typed_array_copyWithin(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSObject *p; int len, to, from, final, count, shift; len = js_typed_array_get_length_internal(ctx, this_val); if (len < 0) return JS_EXCEPTION; if (JS_ToInt32Clamp(ctx, &to, argv[0], 0, len, len)) return JS_EXCEPTION; if (JS_ToInt32Clamp(ctx, &from, argv[1], 0, len, len)) return JS_EXCEPTION; final = len; if (argc > 2 && !JS_IsUndefined(argv[2])) { if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len)) return JS_EXCEPTION; } count = min_int(final - from, len - to); if (count > 0) { p = JS_VALUE_GET_OBJ(this_val); if (typed_array_is_detached(ctx, p)) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); shift = typed_array_size_log2(p->class_id); memmove(p->u.array.u.uint8_ptr + (to << shift), p->u.array.u.uint8_ptr + (from << shift), count << shift); } return JS_DupValue(ctx, this_val); } static JSValue js_typed_array_fill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSObject *p; int len, k, final, shift; uint64_t v64; len = js_typed_array_get_length_internal(ctx, this_val); if (len < 0) return JS_EXCEPTION; p = JS_VALUE_GET_OBJ(this_val); if (p->class_id == JS_CLASS_UINT8C_ARRAY) { int32_t v; if (JS_ToUint8ClampFree(ctx, &v, JS_DupValue(ctx, argv[0]))) return JS_EXCEPTION; v64 = v; } else if (p->class_id <= JS_CLASS_UINT32_ARRAY) { uint32_t v; if (JS_ToUint32(ctx, &v, argv[0])) return JS_EXCEPTION; v64 = v; } else #ifdef CONFIG_BIGNUM if (p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) { if (JS_ToBigInt64(ctx, (int64_t *)&v64, argv[0])) return JS_EXCEPTION; } else #endif { double d; if (JS_ToFloat64(ctx, &d, argv[0])) return JS_EXCEPTION; if (p->class_id == JS_CLASS_FLOAT32_ARRAY) { union { float f; uint32_t u32; } u; u.f = d; v64 = u.u32; } else { JSFloat64Union u; u.d = d; v64 = u.u64; } } k = 0; if (argc > 1) { if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len)) return JS_EXCEPTION; } final = len; if (argc > 2 && !JS_IsUndefined(argv[2])) { if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len)) return JS_EXCEPTION; } if (typed_array_is_detached(ctx, p)) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); shift = typed_array_size_log2(p->class_id); switch(shift) { case 0: if (k < final) { memset(p->u.array.u.uint8_ptr + k, v64, final - k); } break; case 1: for(; k < final; k++) { p->u.array.u.uint16_ptr[k] = v64; } break; case 2: for(; k < final; k++) { p->u.array.u.uint32_ptr[k] = v64; } break; case 3: for(; k < final; k++) { p->u.array.u.uint64_ptr[k] = v64; } break; default: abort(); } return JS_DupValue(ctx, this_val); } static JSValue js_typed_array_find(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int findIndex) { JSValueConst func, this_arg; JSValueConst args[3]; JSValue val, index_val, res; int len, k; val = JS_UNDEFINED; len = js_typed_array_get_length_internal(ctx, this_val); if (len < 0) goto exception; func = argv[0]; if (check_function(ctx, func)) goto exception; this_arg = JS_UNDEFINED; if (argc > 1) this_arg = argv[1]; for(k = 0; k < len; k++) { index_val = JS_NewInt32(ctx, k); val = JS_GetPropertyValue(ctx, this_val, index_val); if (JS_IsException(val)) goto exception; args[0] = val; args[1] = index_val; args[2] = this_val; res = JS_Call(ctx, func, this_arg, 3, args); if (JS_IsException(res)) goto exception; if (JS_ToBoolFree(ctx, res)) { if (findIndex) { JS_FreeValue(ctx, val); return index_val; } else { return val; } } JS_FreeValue(ctx, val); } if (findIndex) return JS_NewInt32(ctx, -1); else return JS_UNDEFINED; exception: JS_FreeValue(ctx, val); return JS_EXCEPTION; } #define special_indexOf 0 #define special_lastIndexOf 1 #define special_includes -1 static JSValue js_typed_array_indexOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int special) { JSObject *p; int len, tag, is_int, is_big, k, stop, inc, res = -1; int64_t v64; double d; float f; len = js_typed_array_get_length_internal(ctx, this_val); if (len < 0) goto exception; if (len == 0) goto done; if (special == special_lastIndexOf) { k = len - 1; if (argc > 1) { if (JS_ToFloat64(ctx, &d, argv[1])) goto exception; if (isnan(d)) { k = 0; } else { if (d >= 0) { if (d < k) { k = d; } } else { d += len; if (d < 0) goto done; k = d; } } } stop = -1; inc = -1; } else { k = 0; if (argc > 1) { if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len)) goto exception; } stop = len; inc = 1; } is_big = 0; is_int = 0; /* avoid warning */ v64 = 0; /* avoid warning */ tag = JS_VALUE_GET_NORM_TAG(argv[0]); if (tag == JS_TAG_INT) { is_int = 1; v64 = JS_VALUE_GET_INT(argv[0]); d = v64; } else if (tag == JS_TAG_FLOAT64) { d = JS_VALUE_GET_FLOAT64(argv[0]); v64 = d; is_int = (v64 == d); } else #ifdef CONFIG_BIGNUM if (tag == JS_TAG_BIG_INT || tag == JS_TAG_BIG_FLOAT) { /* will a generic loop for bigint and bigfloat */ /* XXX: should use the generic loop in math_mode? */ is_big = 1; } else #endif { goto done; } p = JS_VALUE_GET_OBJ(this_val); switch (p->class_id) { case JS_CLASS_INT8_ARRAY: if (is_int && (int8_t)v64 == v64) goto scan8; break; case JS_CLASS_UINT8C_ARRAY: case JS_CLASS_UINT8_ARRAY: if (is_int && (uint8_t)v64 == v64) { const uint8_t *pv, *pp; uint16_t v; scan8: pv = p->u.array.u.uint8_ptr; v = v64; if (inc > 0) { pp = memchr(pv + k, v, len - k); if (pp) res = pp - pv; } else { for (; k != stop; k += inc) { if (pv[k] == v) { res = k; break; } } } } break; case JS_CLASS_INT16_ARRAY: if (is_int && (int16_t)v64 == v64) goto scan16; break; case JS_CLASS_UINT16_ARRAY: if (is_int && (uint16_t)v64 == v64) { const uint16_t *pv; uint16_t v; scan16: pv = p->u.array.u.uint16_ptr; v = v64; for (; k != stop; k += inc) { if (pv[k] == v) { res = k; break; } } } break; case JS_CLASS_INT32_ARRAY: if (is_int && (int32_t)v64 == v64) goto scan32; break; case JS_CLASS_UINT32_ARRAY: if (is_int && (uint32_t)v64 == v64) { const uint32_t *pv; uint32_t v; scan32: pv = p->u.array.u.uint32_ptr; v = v64; for (; k != stop; k += inc) { if (pv[k] == v) { res = k; break; } } } break; case JS_CLASS_FLOAT32_ARRAY: if (is_big) break; if (isnan(d)) { const float *pv = p->u.array.u.float_ptr; /* special case: indexOf returns -1, includes finds NaN */ if (special != special_includes) goto done; for (; k != stop; k += inc) { if (isnan(pv[k])) { res = k; break; } } } else if ((f = (float)d) == d) { const float *pv = p->u.array.u.float_ptr; for (; k != stop; k += inc) { if (pv[k] == f) { res = k; break; } } } break; case JS_CLASS_FLOAT64_ARRAY: if (is_big) break; if (isnan(d)) { const double *pv = p->u.array.u.double_ptr; /* special case: indexOf returns -1, includes finds NaN */ if (special != special_includes) goto done; for (; k != stop; k += inc) { if (isnan(pv[k])) { res = k; break; } } } else { const double *pv = p->u.array.u.double_ptr; for (; k != stop; k += inc) { if (pv[k] == d) { res = k; break; } } } break; #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: case JS_CLASS_BIG_UINT64_ARRAY: if (is_big || is_strict_mode(ctx)) { /* generic loop for bignums, argv[0] is a bignum != NaN */ /* XXX: optimize with explicit values */ for (; k != stop; k += inc) { JSValue v = JS_GetPropertyUint32(ctx, this_val, k); int ret; if (JS_IsException(v)) goto exception; ret = js_same_value_zero(ctx, v, argv[0]); JS_FreeValue(ctx, v); if (ret) { if (ret < 0) goto exception; res = k; break; } } } break; #endif } done: if (special == special_includes) return JS_NewBool(ctx, res >= 0); else return JS_NewInt32(ctx, res); exception: return JS_EXCEPTION; } static JSValue js_typed_array_join(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int toLocaleString) { JSValue sep = JS_UNDEFINED, el; StringBuffer b_s, *b = &b_s; JSString *p = NULL; int i, n; int c; n = js_typed_array_get_length_internal(ctx, this_val); if (n < 0) goto exception; c = ','; /* default separator */ if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) { sep = JS_ToString(ctx, argv[0]); if (JS_IsException(sep)) goto exception; p = JS_VALUE_GET_STRING(sep); if (p->len == 1 && !p->is_wide_char) c = p->u.str8[0]; else c = -1; } string_buffer_init(ctx, b, 0); /* XXX: optimize with direct access */ for(i = 0; i < n; i++) { if (i > 0) { if (c >= 0) { if (string_buffer_putc8(b, c)) goto fail; } else { if (string_buffer_concat(b, p, 0, p->len)) goto fail; } } el = JS_GetPropertyUint32(ctx, this_val, i); if (JS_IsException(el)) goto fail; if (toLocaleString) { el = JS_ToLocaleStringFree(ctx, el); } if (string_buffer_concat_value_free(b, el)) goto fail; } JS_FreeValue(ctx, sep); return string_buffer_end(b); fail: string_buffer_free(b); JS_FreeValue(ctx, sep); exception: return JS_EXCEPTION; } static JSValue js_typed_array_reverse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSObject *p; int len; len = js_typed_array_get_length_internal(ctx, this_val); if (len < 0) return JS_EXCEPTION; if (len > 0) { p = JS_VALUE_GET_OBJ(this_val); switch (typed_array_size_log2(p->class_id)) { case 0: { uint8_t *p1 = p->u.array.u.uint8_ptr; uint8_t *p2 = p1 + len - 1; while (p1 < p2) { uint8_t v = *p1; *p1++ = *p2; *p2-- = v; } } break; case 1: { uint16_t *p1 = p->u.array.u.uint16_ptr; uint16_t *p2 = p1 + len - 1; while (p1 < p2) { uint16_t v = *p1; *p1++ = *p2; *p2-- = v; } } break; case 2: { uint32_t *p1 = p->u.array.u.uint32_ptr; uint32_t *p2 = p1 + len - 1; while (p1 < p2) { uint32_t v = *p1; *p1++ = *p2; *p2-- = v; } } break; case 3: { uint64_t *p1 = p->u.array.u.uint64_ptr; uint64_t *p2 = p1 + len - 1; while (p1 < p2) { uint64_t v = *p1; *p1++ = *p2; *p2-- = v; } } break; default: abort(); } } return JS_DupValue(ctx, this_val); } static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst args[2]; JSValue arr, val; JSObject *p, *p1; int n, len, start, final, count, shift; arr = JS_UNDEFINED; len = js_typed_array_get_length_internal(ctx, this_val); if (len < 0) goto exception; if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) goto exception; final = len; if (!JS_IsUndefined(argv[1])) { if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) goto exception; } count = max_int(final - start, 0); p = get_typed_array(ctx, this_val, 0); if (p == NULL) goto exception; shift = typed_array_size_log2(p->class_id); args[0] = this_val; args[1] = JS_NewInt32(ctx, count); arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args); if (JS_IsException(arr)) goto exception; if (count > 0) { if (validate_typed_array(ctx, this_val) || validate_typed_array(ctx, arr)) goto exception; p1 = get_typed_array(ctx, arr, 0); if (p1 != NULL && p->class_id == p1->class_id && typed_array_get_length(ctx, p1) >= count && typed_array_get_length(ctx, p) >= start + count) { memcpy(p1->u.array.u.uint8_ptr, p->u.array.u.uint8_ptr + (start << shift), count << shift); } else { for (n = 0; n < count; n++) { val = JS_GetPropertyValue(ctx, this_val, JS_NewInt32(ctx, start + n)); if (JS_IsException(val)) goto exception; if (JS_SetPropertyValue(ctx, arr, JS_NewInt32(ctx, n), val, JS_PROP_THROW) < 0) goto exception; } } } return arr; exception: JS_FreeValue(ctx, arr); return JS_EXCEPTION; } static JSValue js_typed_array_subarray(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst args[4]; JSValue arr, byteOffset, ta_buffer; JSObject *p; int len, start, final, count, shift, offset; p = get_typed_array(ctx, this_val, 0); if (!p) goto exception; len = p->u.array.count; if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) goto exception; final = len; if (!JS_IsUndefined(argv[1])) { if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) goto exception; } count = max_int(final - start, 0); byteOffset = js_typed_array_get_byteOffset(ctx, this_val, 0); if (JS_IsException(byteOffset)) goto exception; shift = typed_array_size_log2(p->class_id); offset = JS_VALUE_GET_INT(byteOffset) + (start << shift); JS_FreeValue(ctx, byteOffset); ta_buffer = js_typed_array_get_buffer(ctx, this_val, 0); if (JS_IsException(ta_buffer)) goto exception; args[0] = this_val; args[1] = ta_buffer; args[2] = JS_NewInt32(ctx, offset); args[3] = JS_NewInt32(ctx, count); arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args); JS_FreeValue(ctx, ta_buffer); return arr; exception: return JS_EXCEPTION; } /* TypedArray.prototype.sort */ static int js_cmp_doubles(double x, double y) { if (isnan(x)) return isnan(y) ? 0 : +1; if (isnan(y)) return -1; if (x < y) return -1; if (x > y) return 1; if (x != 0) return 0; if (signbit(x)) return signbit(y) ? 0 : -1; else return signbit(y) ? 1 : 0; } static int js_TA_cmp_int8(const void *a, const void *b, void *opaque) { return *(const int8_t *)a - *(const int8_t *)b; } static int js_TA_cmp_uint8(const void *a, const void *b, void *opaque) { return *(const uint8_t *)a - *(const uint8_t *)b; } static int js_TA_cmp_int16(const void *a, const void *b, void *opaque) { return *(const int16_t *)a - *(const int16_t *)b; } static int js_TA_cmp_uint16(const void *a, const void *b, void *opaque) { return *(const uint16_t *)a - *(const uint16_t *)b; } static int js_TA_cmp_int32(const void *a, const void *b, void *opaque) { int32_t x = *(const int32_t *)a; int32_t y = *(const int32_t *)b; return (y < x) - (y > x); } static int js_TA_cmp_uint32(const void *a, const void *b, void *opaque) { uint32_t x = *(const uint32_t *)a; uint32_t y = *(const uint32_t *)b; return (y < x) - (y > x); } #ifdef CONFIG_BIGNUM static int js_TA_cmp_int64(const void *a, const void *b, void *opaque) { int64_t x = *(const int64_t *)a; int64_t y = *(const int64_t *)b; return (y < x) - (y > x); } static int js_TA_cmp_uint64(const void *a, const void *b, void *opaque) { uint64_t x = *(const uint64_t *)a; uint64_t y = *(const uint64_t *)b; return (y < x) - (y > x); } #endif static int js_TA_cmp_float32(const void *a, const void *b, void *opaque) { return js_cmp_doubles(*(const float *)a, *(const float *)b); } static int js_TA_cmp_float64(const void *a, const void *b, void *opaque) { return js_cmp_doubles(*(const double *)a, *(const double *)b); } static JSValue js_TA_get_int8(JSContext *ctx, const void *a) { return JS_NewInt32(ctx, *(const int8_t *)a); } static JSValue js_TA_get_uint8(JSContext *ctx, const void *a) { return JS_NewInt32(ctx, *(const uint8_t *)a); } static JSValue js_TA_get_int16(JSContext *ctx, const void *a) { return JS_NewInt32(ctx, *(const int16_t *)a); } static JSValue js_TA_get_uint16(JSContext *ctx, const void *a) { return JS_NewInt32(ctx, *(const uint16_t *)a); } static JSValue js_TA_get_int32(JSContext *ctx, const void *a) { return JS_NewInt32(ctx, *(const int32_t *)a); } static JSValue js_TA_get_uint32(JSContext *ctx, const void *a) { return JS_NewUint32(ctx, *(const uint32_t *)a); } #ifdef CONFIG_BIGNUM static JSValue js_TA_get_int64(JSContext *ctx, const void *a) { return JS_NewBigInt64(ctx, *(int64_t *)a); } static JSValue js_TA_get_uint64(JSContext *ctx, const void *a) { return JS_NewBigUint64(ctx, *(uint64_t *)a); } #endif static JSValue js_TA_get_float32(JSContext *ctx, const void *a) { return __JS_NewFloat64(ctx, *(const float *)a); } static JSValue js_TA_get_float64(JSContext *ctx, const void *a) { return __JS_NewFloat64(ctx, *(const double *)a); } struct TA_sort_context { JSContext *ctx; int exception; JSValueConst arr; JSValueConst cmp; JSValue (*getfun)(JSContext *ctx, const void *a); uint8_t *array_ptr; /* cannot change unless the array is detached */ int elt_size; }; static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { struct TA_sort_context *psc = opaque; JSContext *ctx = psc->ctx; uint32_t a_idx, b_idx; JSValueConst argv[2]; JSValue res; int cmp; cmp = 0; if (!psc->exception) { a_idx = *(uint32_t *)a; b_idx = *(uint32_t *)b; argv[0] = psc->getfun(ctx, psc->array_ptr + a_idx * (size_t)psc->elt_size); argv[1] = psc->getfun(ctx, psc->array_ptr + b_idx * (size_t)(psc->elt_size)); res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv); if (JS_IsException(res)) { psc->exception = 1; goto done; } if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) { int val = JS_VALUE_GET_INT(res); cmp = (val > 0) - (val < 0); } else { double val; if (JS_ToFloat64Free(ctx, &val, res) < 0) { psc->exception = 1; goto done; } else { cmp = (val > 0) - (val < 0); } } if (cmp == 0) { /* make sort stable: compare array offsets */ cmp = (a_idx > b_idx) - (a_idx < b_idx); } if (validate_typed_array(ctx, psc->arr) < 0) { psc->exception = 1; } done: JS_FreeValue(ctx, (JSValue)argv[0]); JS_FreeValue(ctx, (JSValue)argv[1]); } return cmp; } static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSObject *p; int len; size_t elt_size; struct TA_sort_context tsc; void *array_ptr; int (*cmpfun)(const void *a, const void *b, void *opaque); tsc.ctx = ctx; tsc.exception = 0; tsc.arr = this_val; tsc.cmp = argv[0]; len = js_typed_array_get_length_internal(ctx, this_val); if (len < 0) return JS_EXCEPTION; if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp)) return JS_EXCEPTION; if (len > 1) { p = JS_VALUE_GET_OBJ(this_val); switch (p->class_id) { case JS_CLASS_INT8_ARRAY: tsc.getfun = js_TA_get_int8; cmpfun = js_TA_cmp_int8; break; case JS_CLASS_UINT8C_ARRAY: case JS_CLASS_UINT8_ARRAY: tsc.getfun = js_TA_get_uint8; cmpfun = js_TA_cmp_uint8; break; case JS_CLASS_INT16_ARRAY: tsc.getfun = js_TA_get_int16; cmpfun = js_TA_cmp_int16; break; case JS_CLASS_UINT16_ARRAY: tsc.getfun = js_TA_get_uint16; cmpfun = js_TA_cmp_uint16; break; case JS_CLASS_INT32_ARRAY: tsc.getfun = js_TA_get_int32; cmpfun = js_TA_cmp_int32; break; case JS_CLASS_UINT32_ARRAY: tsc.getfun = js_TA_get_uint32; cmpfun = js_TA_cmp_uint32; break; #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: tsc.getfun = js_TA_get_int64; cmpfun = js_TA_cmp_int64; break; case JS_CLASS_BIG_UINT64_ARRAY: tsc.getfun = js_TA_get_uint64; cmpfun = js_TA_cmp_uint64; break; #endif case JS_CLASS_FLOAT32_ARRAY: tsc.getfun = js_TA_get_float32; cmpfun = js_TA_cmp_float32; break; case JS_CLASS_FLOAT64_ARRAY: tsc.getfun = js_TA_get_float64; cmpfun = js_TA_cmp_float64; break; default: abort(); } array_ptr = p->u.array.u.ptr; elt_size = 1 << typed_array_size_log2(p->class_id); if (!JS_IsUndefined(tsc.cmp)) { uint32_t *array_idx; void *array_tmp; size_t i, j; /* XXX: a stable sort would use less memory */ array_idx = js_malloc(ctx, len * sizeof(array_idx[0])); if (!array_idx) return JS_EXCEPTION; for(i = 0; i < len; i++) array_idx[i] = i; tsc.array_ptr = array_ptr; tsc.elt_size = elt_size; rqsort(array_idx, len, sizeof(array_idx[0]), js_TA_cmp_generic, &tsc); if (tsc.exception) goto fail; array_tmp = js_malloc(ctx, len * elt_size); if (!array_tmp) { fail: js_free(ctx, array_idx); return JS_EXCEPTION; } memcpy(array_tmp, array_ptr, len * elt_size); switch(elt_size) { case 1: for(i = 0; i < len; i++) { j = array_idx[i]; ((uint8_t *)array_ptr)[i] = ((uint8_t *)array_tmp)[j]; } break; case 2: for(i = 0; i < len; i++) { j = array_idx[i]; ((uint16_t *)array_ptr)[i] = ((uint16_t *)array_tmp)[j]; } break; case 4: for(i = 0; i < len; i++) { j = array_idx[i]; ((uint32_t *)array_ptr)[i] = ((uint32_t *)array_tmp)[j]; } break; case 8: for(i = 0; i < len; i++) { j = array_idx[i]; ((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j]; } break; default: abort(); } js_free(ctx, array_tmp); js_free(ctx, array_idx); } else { rqsort(array_ptr, len, elt_size, cmpfun, &tsc); if (tsc.exception) return JS_EXCEPTION; } } return JS_DupValue(ctx, this_val); } static const JSCFunctionListEntry js_typed_array_base_funcs[] = { JS_CFUNC_DEF("from", 1, js_typed_array_from ), JS_CFUNC_DEF("of", 0, js_typed_array_of ), JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), //JS_CFUNC_DEF("__getLength", 2, js_typed_array___getLength ), //JS_CFUNC_DEF("__create", 2, js_typed_array___create ), //JS_CFUNC_DEF("__speciesCreate", 2, js_typed_array___speciesCreate ), }; static const JSCFunctionListEntry js_typed_array_base_proto_funcs[] = { JS_CGETSET_DEF("length", js_typed_array_get_length, NULL ), JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 0 ), JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 0 ), JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 0 ), JS_CFUNC_DEF("set", 1, js_typed_array_set ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_VALUE ), JS_ALIAS_DEF("[Symbol.iterator]", "values" ), JS_CFUNC_MAGIC_DEF("keys", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY ), JS_CFUNC_MAGIC_DEF("entries", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ), JS_CGETSET_DEF("[Symbol.toStringTag]", js_typed_array_get_toStringTag, NULL ), JS_CFUNC_DEF("copyWithin", 2, js_typed_array_copyWithin ), JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every | special_TA ), JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some | special_TA ), JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach | special_TA ), JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map | special_TA ), JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter | special_TA ), JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce | special_TA ), JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight | special_TA ), JS_CFUNC_DEF("fill", 1, js_typed_array_fill ), JS_CFUNC_MAGIC_DEF("find", 1, js_typed_array_find, 0 ), JS_CFUNC_MAGIC_DEF("findIndex", 1, js_typed_array_find, 1 ), JS_CFUNC_DEF("reverse", 0, js_typed_array_reverse ), JS_CFUNC_DEF("slice", 2, js_typed_array_slice ), JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ), JS_CFUNC_DEF("sort", 1, js_typed_array_sort ), JS_CFUNC_MAGIC_DEF("join", 1, js_typed_array_join, 0 ), JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_typed_array_join, 1 ), JS_CFUNC_MAGIC_DEF("indexOf", 1, js_typed_array_indexOf, special_indexOf ), JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_typed_array_indexOf, special_lastIndexOf ), JS_CFUNC_MAGIC_DEF("includes", 1, js_typed_array_indexOf, special_includes ), //JS_ALIAS_BASE_DEF("toString", "toString", 2 /* Array.prototype. */), @@@ }; static JSValue js_typed_array_base_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_ThrowTypeError(ctx, "cannot be called"); } /* 'obj' must be an allocated typed array object */ static int typed_array_init(JSContext *ctx, JSValueConst obj, JSValue buffer, uint64_t offset, uint64_t len) { JSTypedArray *ta; JSObject *p, *pbuffer; JSArrayBuffer *abuf; int size_log2; p = JS_VALUE_GET_OBJ(obj); size_log2 = typed_array_size_log2(p->class_id); ta = js_malloc(ctx, sizeof(*ta)); if (!ta) { JS_FreeValue(ctx, buffer); return -1; } pbuffer = JS_VALUE_GET_OBJ(buffer); abuf = pbuffer->u.array_buffer; ta->obj = p; ta->buffer = pbuffer; ta->offset = offset; ta->length = len << size_log2; list_add_tail(&ta->link, &abuf->array_list); p->u.typed_array = ta; p->u.array.count = len; p->u.array.u.ptr = abuf->data + offset; return 0; } static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen, JSValueConst obj, JSValueConst method) { JSValue arr, iter, next_method = JS_UNDEFINED, val; BOOL done; uint32_t k; *plen = 0; arr = JS_NewArray(ctx); if (JS_IsException(arr)) return arr; iter = JS_GetIterator2(ctx, obj, method); if (JS_IsException(iter)) goto fail; next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); if (JS_IsException(next_method)) goto fail; k = 0; for(;;) { val = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(val)) goto fail; if (done) { JS_FreeValue(ctx, val); break; } if (JS_CreateDataPropertyUint32(ctx, arr, k, val, JS_PROP_THROW) < 0) goto fail; k++; } JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); *plen = k; return arr; fail: JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, arr); return JS_EXCEPTION; } static JSValue js_typed_array_constructor_obj(JSContext *ctx, JSValueConst new_target, JSValueConst obj, int classid) { JSValue iter, ret, arr = JS_UNDEFINED, val, buffer; uint32_t i; int size_log2; int64_t len; size_log2 = typed_array_size_log2(classid); ret = js_create_from_ctor(ctx, new_target, classid); if (JS_IsException(ret)) return JS_EXCEPTION; iter = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); if (JS_IsException(iter)) goto fail; if (!JS_IsUndefined(iter) && !JS_IsNull(iter)) { uint32_t len1; arr = js_array_from_iterator(ctx, &len1, obj, iter); JS_FreeValue(ctx, iter); if (JS_IsException(arr)) goto fail; len = len1; } else { if (js_get_length64(ctx, &len, obj)) goto fail; arr = JS_DupValue(ctx, obj); } buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, len << size_log2); if (JS_IsException(buffer)) goto fail; if (typed_array_init(ctx, ret, buffer, 0, len)) goto fail; for(i = 0; i < len; i++) { val = JS_GetPropertyUint32(ctx, arr, i); if (JS_IsException(val)) goto fail; if (JS_SetPropertyUint32(ctx, ret, i, val) < 0) goto fail; } JS_FreeValue(ctx, arr); return ret; fail: JS_FreeValue(ctx, arr); JS_FreeValue(ctx, ret); return JS_EXCEPTION; } static JSValue js_typed_array_constructor_ta(JSContext *ctx, JSValueConst new_target, JSValueConst src_obj, int classid) { JSObject *p, *src_buffer; JSTypedArray *ta; JSValue ctor, obj, buffer; uint32_t len, i; int size_log2; JSArrayBuffer *src_abuf, *abuf; obj = js_create_from_ctor(ctx, new_target, classid); if (JS_IsException(obj)) return obj; p = JS_VALUE_GET_OBJ(src_obj); if (typed_array_is_detached(ctx, p)) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } ta = p->u.typed_array; len = p->u.array.count; src_buffer = ta->buffer; src_abuf = src_buffer->u.array_buffer; if (!src_abuf->shared) { ctor = JS_SpeciesConstructor(ctx, JS_MKPTR(JS_TAG_OBJECT, src_buffer), JS_UNDEFINED); if (JS_IsException(ctor)) goto fail; } else { /* force ArrayBuffer default constructor */ ctor = JS_UNDEFINED; } size_log2 = typed_array_size_log2(classid); buffer = js_array_buffer_constructor1(ctx, ctor, (uint64_t)len << size_log2); JS_FreeValue(ctx, ctor); if (JS_IsException(buffer)) goto fail; /* necessary because it could have been detached */ if (typed_array_is_detached(ctx, p)) { JS_FreeValue(ctx, buffer); JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER); if (typed_array_init(ctx, obj, buffer, 0, len)) goto fail; if (p->class_id == classid) { /* same type: copy the content */ memcpy(abuf->data, src_abuf->data + ta->offset, abuf->byte_length); } else { for(i = 0; i < len; i++) { JSValue val; val = JS_GetPropertyUint32(ctx, src_obj, i); if (JS_IsException(val)) goto fail; if (JS_SetPropertyUint32(ctx, obj, i, val) < 0) goto fail; } } return obj; fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } static JSValue js_typed_array_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int classid) { JSValue buffer, obj; JSArrayBuffer *abuf; int size_log2; uint64_t len, offset; size_log2 = typed_array_size_log2(classid); if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) { if (JS_ToIndex(ctx, &len, argv[0])) return JS_EXCEPTION; buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, len << size_log2); if (JS_IsException(buffer)) return JS_EXCEPTION; offset = 0; } else { JSObject *p = JS_VALUE_GET_OBJ(argv[0]); if (p->class_id == JS_CLASS_ARRAY_BUFFER || p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER) { abuf = p->u.array_buffer; if (JS_ToIndex(ctx, &offset, argv[1])) return JS_EXCEPTION; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); if ((offset & ((1 << size_log2) - 1)) != 0 || offset > abuf->byte_length) return JS_ThrowRangeError(ctx, "invalid offset"); if (JS_IsUndefined(argv[2])) { if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) goto invalid_length; len = (abuf->byte_length - offset) >> size_log2; } else { if (JS_ToIndex(ctx, &len, argv[2])) return JS_EXCEPTION; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); if ((offset + (len << size_log2)) > abuf->byte_length) { invalid_length: return JS_ThrowRangeError(ctx, "invalid length"); } } buffer = JS_DupValue(ctx, argv[0]); } else { if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { return js_typed_array_constructor_ta(ctx, new_target, argv[0], classid); } else { return js_typed_array_constructor_obj(ctx, new_target, argv[0], classid); } } } obj = js_create_from_ctor(ctx, new_target, classid); if (JS_IsException(obj)) { JS_FreeValue(ctx, buffer); return JS_EXCEPTION; } if (typed_array_init(ctx, obj, buffer, offset, len)) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } return obj; } static void js_typed_array_finalizer(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSTypedArray *ta = p->u.typed_array; if (ta) { /* during the GC the finalizers are called in an arbitrary order so the ArrayBuffer finalizer may have been called */ if (JS_IsLiveObject(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer))) { list_del(&ta->link); } JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); js_free_rt(rt, ta); } } static void js_typed_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSTypedArray *ta = p->u.typed_array; if (ta) { JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer), mark_func); } } static JSValue js_dataview_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSArrayBuffer *abuf; uint64_t offset; uint32_t len; JSValueConst buffer; JSValue obj; JSTypedArray *ta; JSObject *p; buffer = argv[0]; abuf = js_get_array_buffer(ctx, buffer); if (!abuf) return JS_EXCEPTION; offset = 0; if (argc > 1) { if (JS_ToIndex(ctx, &offset, argv[1])) return JS_EXCEPTION; } if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); if (offset > abuf->byte_length) return JS_ThrowRangeError(ctx, "invalid byteOffset"); len = abuf->byte_length - offset; if (argc > 2 && !JS_IsUndefined(argv[2])) { uint64_t l; if (JS_ToIndex(ctx, &l, argv[2])) return JS_EXCEPTION; if (l > len) return JS_ThrowRangeError(ctx, "invalid byteLength"); len = l; } obj = js_create_from_ctor(ctx, new_target, JS_CLASS_DATAVIEW); if (JS_IsException(obj)) return JS_EXCEPTION; if (abuf->detached) { /* could have been detached in js_create_from_ctor() */ JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } ta = js_malloc(ctx, sizeof(*ta)); if (!ta) { fail: JS_FreeValue(ctx, obj); return JS_EXCEPTION; } p = JS_VALUE_GET_OBJ(obj); ta->obj = p; ta->buffer = JS_VALUE_GET_OBJ(JS_DupValue(ctx, buffer)); ta->offset = offset; ta->length = len; list_add_tail(&ta->link, &abuf->array_list); p->u.typed_array = ta; return obj; } static JSValue js_dataview_getValue(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int class_id) { JSTypedArray *ta; JSArrayBuffer *abuf; int is_swap, size; uint8_t *ptr; uint32_t v; uint64_t pos; ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW); if (!ta) return JS_EXCEPTION; size = 1 << typed_array_size_log2(class_id); if (JS_ToIndex(ctx, &pos, argv[0])) return JS_EXCEPTION; is_swap = FALSE; if (argc > 1) is_swap = JS_ToBool(ctx, argv[1]); #ifndef WORDS_BIGENDIAN is_swap ^= 1; #endif abuf = ta->buffer->u.array_buffer; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); if ((pos + size) > ta->length) return JS_ThrowRangeError(ctx, "out of bound"); ptr = abuf->data + ta->offset + pos; switch(class_id) { case JS_CLASS_INT8_ARRAY: return JS_NewInt32(ctx, *(int8_t *)ptr); case JS_CLASS_UINT8_ARRAY: return JS_NewInt32(ctx, *(uint8_t *)ptr); case JS_CLASS_INT16_ARRAY: v = get_u16(ptr); if (is_swap) v = bswap16(v); return JS_NewInt32(ctx, (int16_t)v); case JS_CLASS_UINT16_ARRAY: v = get_u16(ptr); if (is_swap) v = bswap16(v); return JS_NewInt32(ctx, v); case JS_CLASS_INT32_ARRAY: v = get_u32(ptr); if (is_swap) v = bswap32(v); return JS_NewInt32(ctx, v); case JS_CLASS_UINT32_ARRAY: v = get_u32(ptr); if (is_swap) v = bswap32(v); return JS_NewUint32(ctx, v); #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: { uint64_t v; v = get_u64(ptr); if (is_swap) v = bswap64(v); return JS_NewBigInt64(ctx, v); } break; case JS_CLASS_BIG_UINT64_ARRAY: { uint64_t v; v = get_u64(ptr); if (is_swap) v = bswap64(v); return JS_NewBigUint64(ctx, v); } break; #endif case JS_CLASS_FLOAT32_ARRAY: { union { float f; uint32_t i; } u; v = get_u32(ptr); if (is_swap) v = bswap32(v); u.i = v; return __JS_NewFloat64(ctx, u.f); } case JS_CLASS_FLOAT64_ARRAY: { union { double f; uint64_t i; } u; u.i = get_u64(ptr); if (is_swap) u.i = bswap64(u.i); return __JS_NewFloat64(ctx, u.f); } default: abort(); } } static JSValue js_dataview_setValue(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int class_id) { JSTypedArray *ta; JSArrayBuffer *abuf; int is_swap, size; uint8_t *ptr; uint64_t v64; uint32_t v; uint64_t pos; JSValueConst val; ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW); if (!ta) return JS_EXCEPTION; size = 1 << typed_array_size_log2(class_id); if (JS_ToIndex(ctx, &pos, argv[0])) return JS_EXCEPTION; val = argv[1]; v = 0; /* avoid warning */ v64 = 0; /* avoid warning */ if (class_id <= JS_CLASS_UINT32_ARRAY) { if (JS_ToUint32(ctx, &v, val)) return JS_EXCEPTION; } else #ifdef CONFIG_BIGNUM if (class_id <= JS_CLASS_BIG_UINT64_ARRAY) { if (JS_ToBigInt64(ctx, (int64_t *)&v64, val)) return JS_EXCEPTION; } else #endif { double d; if (JS_ToFloat64(ctx, &d, val)) return JS_EXCEPTION; if (class_id == JS_CLASS_FLOAT32_ARRAY) { union { float f; uint32_t i; } u; u.f = d; v = u.i; } else { JSFloat64Union u; u.d = d; v64 = u.u64; } } is_swap = FALSE; if (argc > 2) is_swap = JS_ToBool(ctx, argv[2]); #ifndef WORDS_BIGENDIAN is_swap ^= 1; #endif abuf = ta->buffer->u.array_buffer; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); if ((pos + size) > ta->length) return JS_ThrowRangeError(ctx, "out of bound"); ptr = abuf->data + ta->offset + pos; switch(class_id) { case JS_CLASS_INT8_ARRAY: case JS_CLASS_UINT8_ARRAY: *ptr = v; break; case JS_CLASS_INT16_ARRAY: case JS_CLASS_UINT16_ARRAY: if (is_swap) v = bswap16(v); put_u16(ptr, v); break; case JS_CLASS_INT32_ARRAY: case JS_CLASS_UINT32_ARRAY: case JS_CLASS_FLOAT32_ARRAY: if (is_swap) v = bswap32(v); put_u32(ptr, v); break; #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: case JS_CLASS_BIG_UINT64_ARRAY: #endif case JS_CLASS_FLOAT64_ARRAY: if (is_swap) v64 = bswap64(v64); put_u64(ptr, v64); break; default: abort(); } return JS_UNDEFINED; } static const JSCFunctionListEntry js_dataview_proto_funcs[] = { JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 1 ), JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 1 ), JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 1 ), JS_CFUNC_MAGIC_DEF("getInt8", 1, js_dataview_getValue, JS_CLASS_INT8_ARRAY ), JS_CFUNC_MAGIC_DEF("getUint8", 1, js_dataview_getValue, JS_CLASS_UINT8_ARRAY ), JS_CFUNC_MAGIC_DEF("getInt16", 1, js_dataview_getValue, JS_CLASS_INT16_ARRAY ), JS_CFUNC_MAGIC_DEF("getUint16", 1, js_dataview_getValue, JS_CLASS_UINT16_ARRAY ), JS_CFUNC_MAGIC_DEF("getInt32", 1, js_dataview_getValue, JS_CLASS_INT32_ARRAY ), JS_CFUNC_MAGIC_DEF("getUint32", 1, js_dataview_getValue, JS_CLASS_UINT32_ARRAY ), #ifdef CONFIG_BIGNUM JS_CFUNC_MAGIC_DEF("getBigInt64", 1, js_dataview_getValue, JS_CLASS_BIG_INT64_ARRAY ), JS_CFUNC_MAGIC_DEF("getBigUint64", 1, js_dataview_getValue, JS_CLASS_BIG_UINT64_ARRAY ), #endif JS_CFUNC_MAGIC_DEF("getFloat32", 1, js_dataview_getValue, JS_CLASS_FLOAT32_ARRAY ), JS_CFUNC_MAGIC_DEF("getFloat64", 1, js_dataview_getValue, JS_CLASS_FLOAT64_ARRAY ), JS_CFUNC_MAGIC_DEF("setInt8", 2, js_dataview_setValue, JS_CLASS_INT8_ARRAY ), JS_CFUNC_MAGIC_DEF("setUint8", 2, js_dataview_setValue, JS_CLASS_UINT8_ARRAY ), JS_CFUNC_MAGIC_DEF("setInt16", 2, js_dataview_setValue, JS_CLASS_INT16_ARRAY ), JS_CFUNC_MAGIC_DEF("setUint16", 2, js_dataview_setValue, JS_CLASS_UINT16_ARRAY ), JS_CFUNC_MAGIC_DEF("setInt32", 2, js_dataview_setValue, JS_CLASS_INT32_ARRAY ), JS_CFUNC_MAGIC_DEF("setUint32", 2, js_dataview_setValue, JS_CLASS_UINT32_ARRAY ), #ifdef CONFIG_BIGNUM JS_CFUNC_MAGIC_DEF("setBigInt64", 2, js_dataview_setValue, JS_CLASS_BIG_INT64_ARRAY ), JS_CFUNC_MAGIC_DEF("setBigUint64", 2, js_dataview_setValue, JS_CLASS_BIG_UINT64_ARRAY ), #endif JS_CFUNC_MAGIC_DEF("setFloat32", 2, js_dataview_setValue, JS_CLASS_FLOAT32_ARRAY ), JS_CFUNC_MAGIC_DEF("setFloat64", 2, js_dataview_setValue, JS_CLASS_FLOAT64_ARRAY ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "DataView", JS_PROP_CONFIGURABLE ), }; /* Atomics */ #ifdef CONFIG_ATOMICS typedef enum AtomicsOpEnum { ATOMICS_OP_ADD, ATOMICS_OP_AND, ATOMICS_OP_OR, ATOMICS_OP_SUB, ATOMICS_OP_XOR, ATOMICS_OP_EXCHANGE, ATOMICS_OP_COMPARE_EXCHANGE, ATOMICS_OP_LOAD, } AtomicsOpEnum; static void *js_atomics_get_ptr(JSContext *ctx, int *psize_log2, JSClassID *pclass_id, JSValueConst obj, JSValueConst idx_val, BOOL is_waitable) { JSObject *p; JSTypedArray *ta; JSArrayBuffer *abuf; void *ptr; uint64_t idx; BOOL err; int size_log2; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) goto fail; p = JS_VALUE_GET_OBJ(obj); #ifdef CONFIG_BIGNUM if (is_waitable) err = (p->class_id != JS_CLASS_INT32_ARRAY && p->class_id != JS_CLASS_BIG_INT64_ARRAY); else err = !(p->class_id >= JS_CLASS_INT8_ARRAY && p->class_id <= JS_CLASS_BIG_UINT64_ARRAY); #else if (is_waitable) err = (p->class_id != JS_CLASS_INT32_ARRAY); else err = !(p->class_id >= JS_CLASS_INT8_ARRAY && p->class_id <= JS_CLASS_UINT32_ARRAY); #endif if (err) { fail: JS_ThrowTypeError(ctx, "integer TypedArray expected"); return NULL; } ta = p->u.typed_array; abuf = ta->buffer->u.array_buffer; if (!abuf->shared) { JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); return NULL; } if (JS_ToIndex(ctx, &idx, idx_val)) { return NULL; } if (idx >= p->u.array.count) { JS_ThrowRangeError(ctx, "out-of-bound access"); return NULL; } size_log2 = typed_array_size_log2(p->class_id); ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); if (psize_log2) *psize_log2 = size_log2; if (pclass_id) *pclass_id = p->class_id; return ptr; } static JSValue js_atomics_op(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int op) { int size_log2; #ifdef CONFIG_BIGNUM uint64_t v, a, rep_val; #else uint32_t v, a, rep_val; #endif void *ptr; JSValue ret; JSClassID class_id; ptr = js_atomics_get_ptr(ctx, &size_log2, &class_id, argv[0], argv[1], FALSE); if (!ptr) return JS_EXCEPTION; rep_val = 0; if (op == ATOMICS_OP_LOAD) { v = 0; } else #ifdef CONFIG_BIGNUM if (size_log2 == 3) { int64_t v64; if (JS_ToBigInt64(ctx, &v64, argv[2])) return JS_EXCEPTION; v = v64; if (op == ATOMICS_OP_COMPARE_EXCHANGE) { if (JS_ToBigInt64(ctx, &v64, argv[3])) return JS_EXCEPTION; rep_val = v64; } } else #endif { uint32_t v32; if (JS_ToUint32(ctx, &v32, argv[2])) return JS_EXCEPTION; v = v32; if (op == ATOMICS_OP_COMPARE_EXCHANGE) { if (JS_ToUint32(ctx, &v32, argv[3])) return JS_EXCEPTION; rep_val = v32; } } switch(op | (size_log2 << 3)) { #ifdef CONFIG_BIGNUM #define OP(op_name, func_name) \ case ATOMICS_OP_ ## op_name | (0 << 3): \ a = func_name((_Atomic(uint8_t) *)ptr, v); \ break; \ case ATOMICS_OP_ ## op_name | (1 << 3): \ a = func_name((_Atomic(uint16_t) *)ptr, v); \ break; \ case ATOMICS_OP_ ## op_name | (2 << 3): \ a = func_name((_Atomic(uint32_t) *)ptr, v); \ break; \ case ATOMICS_OP_ ## op_name | (3 << 3): \ a = func_name((_Atomic(uint64_t) *)ptr, v); \ break; #else #define OP(op_name, func_name) \ case ATOMICS_OP_ ## op_name | (0 << 3): \ a = func_name((_Atomic(uint8_t) *)ptr, v); \ break; \ case ATOMICS_OP_ ## op_name | (1 << 3): \ a = func_name((_Atomic(uint16_t) *)ptr, v); \ break; \ case ATOMICS_OP_ ## op_name | (2 << 3): \ a = func_name((_Atomic(uint32_t) *)ptr, v); \ break; #endif OP(ADD, atomic_fetch_add) OP(AND, atomic_fetch_and) OP(OR, atomic_fetch_or) OP(SUB, atomic_fetch_sub) OP(XOR, atomic_fetch_xor) OP(EXCHANGE, atomic_exchange) #undef OP case ATOMICS_OP_LOAD | (0 << 3): a = atomic_load((_Atomic(uint8_t) *)ptr); break; case ATOMICS_OP_LOAD | (1 << 3): a = atomic_load((_Atomic(uint16_t) *)ptr); break; case ATOMICS_OP_LOAD | (2 << 3): a = atomic_load((_Atomic(uint32_t) *)ptr); break; #ifdef CONFIG_BIGNUM case ATOMICS_OP_LOAD | (3 << 3): a = atomic_load((_Atomic(uint64_t) *)ptr); break; #endif case ATOMICS_OP_COMPARE_EXCHANGE | (0 << 3): { uint8_t v1 = v; atomic_compare_exchange_strong((_Atomic(uint8_t) *)ptr, &v1, rep_val); a = v1; } break; case ATOMICS_OP_COMPARE_EXCHANGE | (1 << 3): { uint16_t v1 = v; atomic_compare_exchange_strong((_Atomic(uint16_t) *)ptr, &v1, rep_val); a = v1; } break; case ATOMICS_OP_COMPARE_EXCHANGE | (2 << 3): { uint32_t v1 = v; atomic_compare_exchange_strong((_Atomic(uint32_t) *)ptr, &v1, rep_val); a = v1; } break; #ifdef CONFIG_BIGNUM case ATOMICS_OP_COMPARE_EXCHANGE | (3 << 3): { uint64_t v1 = v; atomic_compare_exchange_strong((_Atomic(uint64_t) *)ptr, &v1, rep_val); a = v1; } break; #endif default: abort(); } switch(class_id) { case JS_CLASS_INT8_ARRAY: a = (int8_t)a; goto done; case JS_CLASS_UINT8_ARRAY: a = (uint8_t)a; goto done; case JS_CLASS_INT16_ARRAY: a = (int16_t)a; goto done; case JS_CLASS_UINT16_ARRAY: a = (uint16_t)a; goto done; case JS_CLASS_INT32_ARRAY: done: ret = JS_NewInt32(ctx, a); break; case JS_CLASS_UINT32_ARRAY: ret = JS_NewUint32(ctx, a); break; #ifdef CONFIG_BIGNUM case JS_CLASS_BIG_INT64_ARRAY: ret = JS_NewBigInt64(ctx, a); break; case JS_CLASS_BIG_UINT64_ARRAY: ret = JS_NewBigUint64(ctx, a); break; #endif default: abort(); } return ret; } static JSValue js_atomics_store(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) { int size_log2; void *ptr; JSValue ret; ptr = js_atomics_get_ptr(ctx, &size_log2, NULL, argv[0], argv[1], FALSE); if (!ptr) return JS_EXCEPTION; #ifdef CONFIG_BIGNUM if (size_log2 == 3) { int64_t v64; ret = JS_ToBigIntValueFree(ctx, JS_DupValue(ctx, argv[2])); if (JS_IsException(ret)) return ret; if (JS_ToBigInt64(ctx, &v64, ret)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } atomic_store((_Atomic(uint64_t) *)ptr, v64); } else #endif { uint32_t v; /* XXX: spec, would be simpler to return the written value */ ret = JS_ToIntegerFree(ctx, JS_DupValue(ctx, argv[2])); if (JS_IsException(ret)) return ret; if (JS_ToUint32(ctx, &v, ret)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } switch(size_log2) { case 0: atomic_store((_Atomic(uint8_t) *)ptr, v); break; case 1: atomic_store((_Atomic(uint16_t) *)ptr, v); break; case 2: atomic_store((_Atomic(uint32_t) *)ptr, v); break; default: abort(); } } return ret; } static JSValue js_atomics_isLockFree(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) { int v, ret; if (JS_ToInt32Sat(ctx, &v, argv[0])) return JS_EXCEPTION; ret = (v == 1 || v == 2 || v == 4 #ifdef CONFIG_BIGNUM || v == 8 #endif ); return JS_NewBool(ctx, ret); } typedef struct JSAtomicsWaiter { struct list_head link; BOOL linked; pthread_cond_t cond; int32_t *ptr; } JSAtomicsWaiter; static pthread_mutex_t js_atomics_mutex = PTHREAD_MUTEX_INITIALIZER; static struct list_head js_atomics_waiter_list = LIST_HEAD_INIT(js_atomics_waiter_list); static JSValue js_atomics_wait(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) { int64_t v; int32_t v32; void *ptr; int64_t timeout; struct timespec ts; JSAtomicsWaiter waiter_s, *waiter; int ret, size_log2, res; double d; ptr = js_atomics_get_ptr(ctx, &size_log2, NULL, argv[0], argv[1], TRUE); if (!ptr) return JS_EXCEPTION; #ifdef CONFIG_BIGNUM if (size_log2 == 3) { if (JS_ToBigInt64(ctx, &v, argv[2])) return JS_EXCEPTION; } else #endif { if (JS_ToInt32(ctx, &v32, argv[2])) return JS_EXCEPTION; v = v32; } if (JS_ToFloat64(ctx, &d, argv[3])) return JS_EXCEPTION; if (isnan(d) || d > INT64_MAX) timeout = INT64_MAX; else if (d < 0) timeout = 0; else timeout = (int64_t)d; if (!ctx->rt->can_block) return JS_ThrowTypeError(ctx, "cannot block in this thread"); /* XXX: inefficient if large number of waiters, should hash on 'ptr' value */ /* XXX: use Linux futexes when available ? */ pthread_mutex_lock(&js_atomics_mutex); if (size_log2 == 3) { res = *(int64_t *)ptr != v; } else { res = *(int32_t *)ptr != v; } if (res) { pthread_mutex_unlock(&js_atomics_mutex); return JS_AtomToString(ctx, JS_ATOM_not_equal); } waiter = &waiter_s; waiter->ptr = ptr; pthread_cond_init(&waiter->cond, NULL); waiter->linked = TRUE; list_add_tail(&waiter->link, &js_atomics_waiter_list); if (timeout == INT64_MAX) { pthread_cond_wait(&waiter->cond, &js_atomics_mutex); ret = 0; } else { /* XXX: use clock monotonic */ clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += timeout / 1000; ts.tv_nsec += (timeout % 1000) * 1000000; if (ts.tv_nsec >= 1000000000) { ts.tv_nsec -= 1000000000; ts.tv_sec++; } ret = pthread_cond_timedwait(&waiter->cond, &js_atomics_mutex, &ts); } if (waiter->linked) list_del(&waiter->link); pthread_mutex_unlock(&js_atomics_mutex); pthread_cond_destroy(&waiter->cond); if (ret == ETIMEDOUT) { return JS_AtomToString(ctx, JS_ATOM_timed_out); } else { return JS_AtomToString(ctx, JS_ATOM_ok); } } static JSValue js_atomics_notify(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) { struct list_head *el, *el1, waiter_list; int32_t count, n; void *ptr; JSAtomicsWaiter *waiter; ptr = js_atomics_get_ptr(ctx, NULL, NULL, argv[0], argv[1], TRUE); if (!ptr) return JS_EXCEPTION; if (JS_IsUndefined(argv[2])) { count = INT32_MAX; } else { if (JS_ToInt32Clamp(ctx, &count, argv[2], 0, INT32_MAX, 0)) return JS_EXCEPTION; } n = 0; if (count > 0) { pthread_mutex_lock(&js_atomics_mutex); init_list_head(&waiter_list); list_for_each_safe(el, el1, &js_atomics_waiter_list) { waiter = list_entry(el, JSAtomicsWaiter, link); if (waiter->ptr == ptr) { list_del(&waiter->link); waiter->linked = FALSE; list_add_tail(&waiter->link, &waiter_list); n++; if (n >= count) break; } } list_for_each(el, &waiter_list) { waiter = list_entry(el, JSAtomicsWaiter, link); pthread_cond_signal(&waiter->cond); } pthread_mutex_unlock(&js_atomics_mutex); } return JS_NewInt32(ctx, n); } static const JSCFunctionListEntry js_atomics_funcs[] = { JS_CFUNC_MAGIC_DEF("add", 3, js_atomics_op, ATOMICS_OP_ADD ), JS_CFUNC_MAGIC_DEF("and", 3, js_atomics_op, ATOMICS_OP_AND ), JS_CFUNC_MAGIC_DEF("or", 3, js_atomics_op, ATOMICS_OP_OR ), JS_CFUNC_MAGIC_DEF("sub", 3, js_atomics_op, ATOMICS_OP_SUB ), JS_CFUNC_MAGIC_DEF("xor", 3, js_atomics_op, ATOMICS_OP_XOR ), JS_CFUNC_MAGIC_DEF("exchange", 3, js_atomics_op, ATOMICS_OP_EXCHANGE ), JS_CFUNC_MAGIC_DEF("compareExchange", 4, js_atomics_op, ATOMICS_OP_COMPARE_EXCHANGE ), JS_CFUNC_MAGIC_DEF("load", 2, js_atomics_op, ATOMICS_OP_LOAD ), JS_CFUNC_DEF("store", 3, js_atomics_store ), JS_CFUNC_DEF("isLockFree", 1, js_atomics_isLockFree ), JS_CFUNC_DEF("wait", 4, js_atomics_wait ), JS_CFUNC_DEF("notify", 3, js_atomics_notify ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Atomics", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_atomics_obj[] = { JS_OBJECT_DEF("Atomics", js_atomics_funcs, countof(js_atomics_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), }; void JS_AddIntrinsicAtomics(JSContext *ctx) { /* add Atomics as autoinit object */ JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_atomics_obj, countof(js_atomics_obj)); } #endif /* CONFIG_ATOMICS */ void JS_AddIntrinsicTypedArrays(JSContext *ctx) { JSValue typed_array_base_proto, typed_array_base_func; JSValueConst array_buffer_func, shared_array_buffer_func; int i; ctx->class_proto[JS_CLASS_ARRAY_BUFFER] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_BUFFER], js_array_buffer_proto_funcs, countof(js_array_buffer_proto_funcs)); array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "ArrayBuffer", js_array_buffer_constructor, 1, ctx->class_proto[JS_CLASS_ARRAY_BUFFER]); JS_SetPropertyFunctionList(ctx, array_buffer_func, js_array_buffer_funcs, countof(js_array_buffer_funcs)); ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER], js_shared_array_buffer_proto_funcs, countof(js_shared_array_buffer_proto_funcs)); shared_array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "SharedArrayBuffer", js_shared_array_buffer_constructor, 1, ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER]); JS_SetPropertyFunctionList(ctx, shared_array_buffer_func, js_shared_array_buffer_funcs, countof(js_shared_array_buffer_funcs)); typed_array_base_proto = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, typed_array_base_proto, js_typed_array_base_proto_funcs, countof(js_typed_array_base_proto_funcs)); /* TypedArray.prototype.toString must be the same object as Array.prototype.toString */ JSValue obj = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_toString); /* XXX: should use alias method in JSCFunctionListEntry */ //@@@ JS_DefinePropertyValue(ctx, typed_array_base_proto, JS_ATOM_toString, obj, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); typed_array_base_func = JS_NewCFunction(ctx, js_typed_array_base_constructor, "TypedArray", 0); JS_SetPropertyFunctionList(ctx, typed_array_base_func, js_typed_array_base_funcs, countof(js_typed_array_base_funcs)); JS_SetConstructor(ctx, typed_array_base_func, typed_array_base_proto); for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) { JSValue func_obj; char buf[ATOM_GET_STR_BUF_SIZE]; const char *name; ctx->class_proto[i] = JS_NewObjectProto(ctx, typed_array_base_proto); JS_DefinePropertyValueStr(ctx, ctx->class_proto[i], "BYTES_PER_ELEMENT", JS_NewInt32(ctx, 1 << typed_array_size_log2(i)), 0); name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); func_obj = JS_NewCFunction3(ctx, (JSCFunction *)js_typed_array_constructor, name, 3, JS_CFUNC_constructor_magic, i, typed_array_base_func); JS_NewGlobalCConstructor2(ctx, func_obj, name, ctx->class_proto[i]); JS_DefinePropertyValueStr(ctx, func_obj, "BYTES_PER_ELEMENT", JS_NewInt32(ctx, 1 << typed_array_size_log2(i)), 0); } JS_FreeValue(ctx, typed_array_base_proto); JS_FreeValue(ctx, typed_array_base_func); /* DataView */ ctx->class_proto[JS_CLASS_DATAVIEW] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATAVIEW], js_dataview_proto_funcs, countof(js_dataview_proto_funcs)); JS_NewGlobalCConstructorOnly(ctx, "DataView", js_dataview_constructor, 1, ctx->class_proto[JS_CLASS_DATAVIEW]); /* Atomics */ #ifdef CONFIG_ATOMICS JS_AddIntrinsicAtomics(ctx); #endif }