quickjs/quickjs.c

55810 lines
1.7 MiB
C
Raw Permalink Normal View History

2020-09-06 18:53:08 +02:00
/*
* QuickJS Javascript Engine
2024-02-10 16:18:11 +01:00
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
2020-09-06 18:53:08 +02:00
*
* 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 <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <inttypes.h>
#include <string.h>
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#include <fenv.h>
#include <math.h>
#if defined(__APPLE__)
#include <malloc/malloc.h>
#elif defined(__linux__) || defined(__GLIBC__)
2020-09-06 18:53:08 +02:00
#include <malloc.h>
2021-03-27 11:17:31 +01:00
#elif defined(__FreeBSD__)
#include <malloc_np.h>
2020-09-06 18:53:08 +02:00
#endif
#include "cutils.h"
#include "list.h"
#include "quickjs.h"
#include "libregexp.h"
#include "libunicode.h"
#include "dtoa.h"
2020-09-06 18:53:08 +02:00
#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
2020-09-06 19:07:30 +02:00
#if !defined(EMSCRIPTEN)
/* enable stack limitation */
#define CONFIG_STACK_CHECK
#endif
2020-09-06 18:53:08 +02:00
/* dump object free */
//#define DUMP_FREE
//#define DUMP_CLOSURE
/* dump the bytecode of the compiled functions: combination of bits
1: dump pass 3 final byte code
2: dump pass 2 code
4: dump pass 1 code
8: dump stdlib functions
16: dump bytecode in hex
32: dump line number table
64: dump compute_stack_size
2020-09-06 18:53:08 +02:00
*/
//#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
//#define DUMP_ROPE_REBALANCE
2020-09-06 18:53:08 +02:00
/* test the GC by forcing it before each object allocation */
//#define FORCE_GC_AT_MALLOC
#ifdef CONFIG_ATOMICS
#include <pthread.h>
#include <stdatomic.h>
#include <errno.h>
#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) */
JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */
JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */
JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */
2020-09-06 18:53:08 +02:00
JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */
JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */
JS_CLASS_DATAVIEW, /* u.typed_array */
JS_CLASS_BIG_INT, /* u.object_data */
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_WEAK_REF,
JS_CLASS_FINALIZATION_REGISTRY,
2020-09-06 18:53:08 +02:00
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,
2020-09-06 19:04:20 +02:00
JS_AGGREGATE_ERROR,
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */
} JSErrorEnum;
/* the variable and scope indexes must fit on 16 bits. The (-1) and
ARG_SCOPE_END values are reserved. */
#define JS_MAX_LOCAL_VARS 65534
2020-11-08 14:30:56 +01:00
#define JS_STACK_SIZE_MAX 65534
2020-09-06 18:53:08 +02:00
#define JS_STRING_LEN_MAX ((1 << 30) - 1)
/* strings <= this length are not concatenated using ropes. if too
small, the rope memory overhead becomes high. */
#define JS_STRING_ROPE_SHORT_LEN 512
/* specific threshold for initial rope use */
#define JS_STRING_ROPE_SHORT2_LEN 8192
/* rope depth at which we rebalance */
#define JS_STRING_ROPE_MAX_DEPTH 60
2020-09-06 18:53:08 +02:00
#define __exception __attribute__((warn_unused_result))
typedef struct JSShape JSShape;
typedef struct JSString JSString;
typedef struct JSString JSAtomStruct;
typedef struct JSObject JSObject;
#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v))
#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v))
#define JS_VALUE_GET_STRING_ROPE(v) ((JSStringRope *)JS_VALUE_GET_PTR(v))
2020-09-06 18:53:08 +02:00
typedef enum {
JS_GC_PHASE_NONE,
JS_GC_PHASE_DECREF,
JS_GC_PHASE_REMOVE_CYCLES,
} JSGCPhaseEnum;
typedef enum OPCodeEnum OPCodeEnum;
struct JSRuntime {
JSMallocFunctions mf;
JSMallocState malloc_state;
const char *rt_info;
int atom_hash_size; /* power of two */
int atom_count;
int atom_size;
int atom_count_resize; /* resize hash table at this count */
uint32_t *atom_hash;
JSAtomStruct **atom_array;
int atom_free_index; /* 0 = none */
int class_count; /* size of class_array */
JSClass *class_array;
struct list_head context_list; /* list of JSContext.link */
/* list of JSGCObjectHeader.link. List of allocated GC objects (used
by the garbage collector) */
struct list_head gc_obj_list;
/* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */
2024-02-10 16:18:11 +01:00
struct list_head gc_zero_ref_count_list;
2020-09-06 18:53:08 +02:00
struct list_head tmp_obj_list; /* used during GC */
JSGCPhaseEnum gc_phase : 8;
size_t malloc_gc_threshold;
struct list_head weakref_list; /* list of JSWeakRefHeader.link */
2020-09-06 18:53:08 +02:00
#ifdef DUMP_LEAKS
struct list_head string_list; /* list of JSString.link */
#endif
2020-09-06 19:04:20 +02:00
/* stack limitation */
2021-03-27 11:17:31 +01:00
uintptr_t stack_size; /* in bytes, 0 if no limit */
uintptr_t stack_top;
uintptr_t stack_limit; /* lower stack limit */
2024-02-10 16:18:11 +01:00
2020-09-06 19:04:20 +02:00
JSValue current_exception;
/* true if the current exception cannot be catched */
BOOL current_exception_is_uncatchable : 8;
2020-09-06 19:04:20 +02:00
/* true if inside an out of memory error, to avoid recursing */
BOOL in_out_of_memory : 8;
struct JSStackFrame *current_stack_frame;
2020-09-06 18:53:08 +02:00
JSInterruptHandler *interrupt_handler;
void *interrupt_opaque;
JSHostPromiseRejectionTracker *host_promise_rejection_tracker;
void *host_promise_rejection_tracker_opaque;
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
struct list_head job_list; /* list of JSJobEntry.link */
JSModuleNormalizeFunc *module_normalize_func;
BOOL module_loader_has_attr;
union {
JSModuleLoaderFunc *module_loader_func;
JSModuleLoaderFunc2 *module_loader_func2;
} u;
JSModuleCheckSupportedImportAttributes *module_check_attrs;
2020-09-06 18:53:08 +02:00
void *module_loader_opaque;
/* timestamp for internal use in module evaluation */
int64_t module_async_evaluation_next_timestamp;
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
2020-09-06 19:07:30 +02:00
/* used to allocate, free and clone SharedArrayBuffers */
JSSharedArrayBufferFunctions sab_funcs;
/* see JS_SetStripInfo() */
uint8_t strip_flags;
2020-09-06 18:53:08 +02:00
/* Shape hash table */
int shape_hash_bits;
int shape_hash_size;
int shape_hash_count; /* number of hashed shapes */
JSShape **shape_hash;
2020-09-06 19:02:03 +02:00
void *user_opaque;
2020-09-06 18:53:08 +02:00
};
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)
2025-03-19 11:43:31 +01:00
#define JS_MODE_ASYNC (1 << 2) /* async function */
#define JS_MODE_BACKTRACE_BARRIER (1 << 3) /* stop backtrace before this frame */
2020-09-06 18:53:08 +02:00
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.var_ref_link */
2020-09-06 18:53:08 +02:00
const uint8_t *cur_pc; /* only used in bytecode functions : PC of the
instruction after the call */
int arg_count;
2025-03-19 11:43:31 +01:00
int js_mode; /* not supported for C functions */
2020-09-06 18:53:08 +02:00
/* only used in generators. Current stack pointer value. NULL if
2024-02-10 16:18:11 +01:00
the function is running. */
2020-09-06 18:53:08 +02:00
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,
2020-09-06 19:04:20 +02:00
JS_GC_OBJ_TYPE_JS_CONTEXT,
2020-09-06 18:53:08 +02:00
} 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 enum {
JS_WEAKREF_TYPE_MAP,
JS_WEAKREF_TYPE_WEAKREF,
JS_WEAKREF_TYPE_FINREC,
} JSWeakRefHeaderTypeEnum;
typedef struct {
struct list_head link;
JSWeakRefHeaderTypeEnum weakref_type;
} JSWeakRefHeader;
2020-09-06 18:53:08 +02:00
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 */
2025-03-13 17:44:55 +01:00
uint8_t is_detached;
2020-09-06 18:53:08 +02:00
};
};
JSValue *pvalue; /* pointer to the value, either on the stack or
to 'value' */
union {
JSValue value; /* used when is_detached = TRUE */
struct {
struct list_head var_ref_link; /* JSStackFrame.var_ref_list list */
struct JSAsyncFunctionState *async_func; /* != NULL if async stack frame */
}; /* used when is_detached = FALSE */
};
2020-09-06 18:53:08 +02:00
} JSVarRef;
/* bigint */
2020-09-06 18:53:08 +02:00
#if JS_LIMB_BITS == 32
typedef int32_t js_slimb_t;
typedef uint32_t js_limb_t;
typedef int64_t js_sdlimb_t;
typedef uint64_t js_dlimb_t;
#define JS_LIMB_DIGITS 9
#else
typedef __int128 int128_t;
typedef unsigned __int128 uint128_t;
typedef int64_t js_slimb_t;
typedef uint64_t js_limb_t;
typedef int128_t js_sdlimb_t;
typedef uint128_t js_dlimb_t;
#define JS_LIMB_DIGITS 19
2020-09-06 18:53:08 +02:00
#endif
typedef struct JSBigInt {
JSRefCountHeader header; /* must come first, 32-bit */
uint32_t len; /* number of limbs, >= 1 */
js_limb_t tab[]; /* two's complement representation, always
normalized so that 'len' is the minimum
possible length >= 1 */
} JSBigInt;
/* this bigint structure can hold a 64 bit integer */
typedef struct {
2025-03-18 18:45:21 +01:00
js_limb_t big_int_buf[sizeof(JSBigInt) / sizeof(js_limb_t)]; /* for JSBigInt */
/* must come just after */
js_limb_t tab[(64 + JS_LIMB_BITS - 1) / JS_LIMB_BITS];
} JSBigIntBuf;
2020-09-06 19:04:20 +02:00
typedef enum {
JS_AUTOINIT_ID_PROTOTYPE,
JS_AUTOINIT_ID_MODULE_NS,
2020-09-06 19:07:30 +02:00
JS_AUTOINIT_ID_PROP,
2020-09-06 19:04:20 +02:00
} JSAutoInitIDEnum;
2020-09-06 18:53:08 +02:00
/* 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 {
2020-09-06 19:04:20 +02:00
JSGCObjectHeader header; /* must come first */
2020-09-06 18:53:08 +02:00
JSRuntime *rt;
struct list_head link;
uint16_t binary_object_count;
int binary_object_size;
JSShape *array_shape; /* initial shape for Array objects */
JSValue *class_proto;
JSValue function_proto;
JSValue function_ctor;
2020-09-06 19:04:20 +02:00
JSValue array_ctor;
2020-09-06 18:53:08 +02:00
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;
2020-09-06 18:53:08 +02:00
/* when the counter reaches zero, JSRutime.interrupt_handler is called */
int interrupt_counter;
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,
};
typedef enum {
JS_ATOM_KIND_STRING,
JS_ATOM_KIND_SYMBOL,
JS_ATOM_KIND_PRIVATE,
} JSAtomKindEnum;
#define JS_ATOM_HASH_MASK ((1 << 30) - 1)
#define JS_ATOM_HASH_PRIVATE JS_ATOM_HASH_MASK
2020-09-06 18:53:08 +02:00
struct JSString {
JSRefCountHeader header; /* must come first, 32-bit */
uint32_t len : 31;
uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */
/* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3,
for JS_ATOM_TYPE_PRIVATE: hash = JS_ATOM_HASH_PRIVATE, atom_type = 3
2020-09-06 18:53:08 +02:00
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 JSStringRope {
JSRefCountHeader header; /* must come first, 32-bit */
uint32_t len;
uint8_t is_wide_char; /* 0 = 8 bits, 1 = 16 bits characters */
uint8_t depth; /* max depth of the rope tree */
/* XXX: could reduce memory usage by using a direct pointer with
bit 0 to select rope or string */
JSValue left;
JSValue right; /* might be the empty string */
} JSStringRope;
2020-09-06 18:53:08 +02:00
typedef struct JSClosureVar {
uint8_t is_local : 1;
uint8_t is_arg : 1;
uint8_t is_const : 1;
uint8_t is_lexical : 1;
2020-11-08 14:30:56 +01:00
uint8_t var_kind : 4; /* see JSVarKindEnum */
/* 8 bits available */
2020-09-06 18:53:08 +02:00
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;
2020-11-08 14:30:56 +01:00
#define ARG_SCOPE_INDEX 1
#define ARG_SCOPE_END (-2)
2020-09-06 18:53:08 +02:00
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,
2020-11-08 14:30:56 +01:00
JS_VAR_FUNCTION_NAME, /* function expression name */
2020-09-06 18:53:08 +02:00
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;
2020-11-08 14:30:56 +01:00
/* XXX: could use a different structure in bytecode functions to save
memory */
2020-09-06 18:53:08 +02:00
typedef struct JSVarDef {
JSAtom var_name;
2020-11-08 14:30:56 +01:00
/* index into fd->scopes of this variable lexical scope */
int scope_level;
2024-02-10 16:18:11 +01:00
/* during compilation:
2020-11-08 14:30:56 +01:00
- if scope_level = 0: scope in which the variable is defined
- if scope_level != 0: index into fd->vars of the next
variable in the same or enclosing lexical scope
in a bytecode function:
index into fd->vars of the next
variable in the same or enclosing lexical scope
*/
2024-02-10 16:18:11 +01:00
int scope_next;
2020-09-06 18:53:08 +02:00
uint8_t is_const : 1;
uint8_t is_lexical : 1;
uint8_t is_captured : 1;
uint8_t is_static_private : 1; /* only used during private class field parsing */
2020-09-06 18:53:08 +02:00
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) */
2020-11-08 14:30:56 +01:00
int func_pool_idx : 24; /* only used during compilation : index in
the constant pool for hoisted function
definition */
2020-09-06 18:53:08 +02:00
} 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 read_only_bytecode : 1;
2024-02-10 16:18:11 +01:00
uint8_t is_direct_or_indirect_eval : 1; /* used by JS_GetScriptOrModuleName() */
/* XXX: 10 bits available */
2020-09-06 18:53:08 +02:00
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 */
2020-09-06 19:04:20 +02:00
JSContext *realm; /* function realm */
2020-09-06 18:53:08 +02:00
JSValue *cpool; /* constant pool (self pointer) */
int cpool_count;
int closure_var_count;
struct {
/* debug info, move to separate structure to save memory? */
JSAtom filename;
int source_len;
2020-09-06 18:53:08 +02:00
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;
uint32_t idx;
uint32_t atom_count;
uint8_t in_prototype_chain;
uint8_t is_array;
JSPropertyEnum *tab_atom; /* is_array = FALSE */
2020-09-06 18:53:08 +02:00
} JSForInIterator;
typedef struct JSRegExp {
JSString *pattern;
JSString *bytecode; /* also contains the flags */
} JSRegExp;
typedef struct JSProxyData {
JSValue target;
JSValue handler;
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 {
JSGCObjectHeader header;
JSValue this_val; /* 'this' argument */
2020-09-06 18:53:08 +02:00
int argc; /* number of function arguments */
BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */
BOOL is_completed; /* TRUE if the function has returned. The stack
frame is no longer valid */
JSValue resolving_funcs[2]; /* only used in JS async functions */
2020-09-06 18:53:08 +02:00
JSStackFrame frame;
} JSAsyncFunctionState;
2020-09-06 18:57:11 +02:00
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;
2020-09-06 18:53:08 +02:00
typedef struct JSReqModuleEntry {
JSAtom module_name;
JSModuleDef *module; /* used using resolution */
JSValue attributes; /* JS_UNDEFINED or an object contains the attributes as key/value */
2020-09-06 18:53:08 +02:00
} 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 */
BOOL is_star; /* import_name = '*' is a valid import name, so need a flag */
2020-09-06 18:53:08 +02:00
JSAtom import_name;
int req_module_idx; /* in req_module_entries */
} JSImportEntry;
typedef enum {
JS_MODULE_STATUS_UNLINKED,
JS_MODULE_STATUS_LINKING,
JS_MODULE_STATUS_LINKED,
JS_MODULE_STATUS_EVALUATING,
JS_MODULE_STATUS_EVALUATING_ASYNC,
JS_MODULE_STATUS_EVALUATED,
} JSModuleStatus;
2020-09-06 18:53:08 +02:00
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 has_tla : 8; /* true if func_obj contains await */
2020-09-06 18:53:08 +02:00
BOOL resolved : 8;
2020-09-06 19:07:30 +02:00
BOOL func_created : 8;
JSModuleStatus status : 8;
/* temp use during js_module_link() & js_module_evaluate() */
int dfs_index, dfs_ancestor_index;
JSModuleDef *stack_prev;
/* temp use during js_module_evaluate() */
JSModuleDef **async_parent_modules;
int async_parent_modules_count;
int async_parent_modules_size;
int pending_async_dependencies;
BOOL async_evaluation;
int64_t async_evaluation_timestamp;
JSModuleDef *cycle_root;
JSValue promise; /* corresponds to spec field: capability */
JSValue resolving_funcs[2]; /* corresponds to spec field: capability */
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
/* true if evaluation yielded an exception. It is saved in
eval_exception */
2024-02-10 16:18:11 +01:00
BOOL eval_has_exception : 8;
2020-09-06 18:53:08 +02:00
JSValue eval_exception;
JSValue meta_obj; /* for import.meta */
JSValue private_value; /* private value for C modules */
2020-09-06 18:53:08 +02:00
};
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 */
2020-09-06 19:04:20 +02:00
/* in order to use only 2 pointers, we compress the realm
and the init function pointer */
2020-09-06 19:07:30 +02:00
uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x)
in the 2 low bits */
2020-09-06 18:53:08 +02:00
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 {
2020-09-06 19:10:15 +02:00
/* hash table of size hash_mask + 1 before the start of the
structure (see prop_hash_end()). */
2020-09-06 18:53:08 +02:00
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 */
2020-09-06 19:07:30 +02:00
int prop_count; /* include deleted properties */
int deleted_prop_count;
2020-09-06 18:53:08 +02:00
JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */
JSObject *proto;
JSShapeProperty prop[0]; /* prop_size elements */
};
2020-09-06 19:07:30 +02:00
2020-09-06 18:53:08 +02:00
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 */
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
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 */
2020-11-08 14:30:56 +01:00
uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */
2020-09-06 18:53:08 +02:00
uint8_t is_constructor : 1; /* TRUE if object is a constructor function */
uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */
2020-09-06 18:53:08 +02:00
uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */
2020-09-06 19:10:15 +02:00
uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */
2020-09-06 18:53:08 +02:00
uint16_t class_id; /* see JS_CLASS_x */
};
};
/* count the number of weak references to this object. The object
structure is freed only if header.ref_count = 0 and
weakref_count = 0 */
uint32_t weakref_count;
2020-09-06 18:53:08 +02:00
JSShape *shape; /* prototype and property names + flag */
JSProperty *prop; /* array of properties */
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 */
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 JSAsyncFunctionState *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */
2020-09-06 18:53:08 +02:00
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;
2020-09-06 19:04:20 +02:00
struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */
JSContext *realm;
2020-09-06 18:53:08 +02:00
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 {
2024-02-10 16:18:11 +01:00
JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */
2020-09-06 18:53:08 +02:00
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 */
uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */
2020-09-06 18:53:08 +02:00
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;
};
typedef struct JSMapRecord {
int ref_count; /* used during enumeration to avoid freeing the record */
BOOL empty : 8; /* TRUE if the record is deleted */
struct list_head link;
struct JSMapRecord *hash_next;
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;
JSMapRecord **hash_table;
int hash_bits;
uint32_t hash_size; /* = 2 ^ hash_bits */
uint32_t record_count_threshold; /* count at which a hash table
resize is needed */
JSWeakRefHeader weakref_header; /* only used if is_weak = TRUE */
} JSMapState;
2020-09-06 18:53:08 +02:00
enum {
2020-09-06 19:10:15 +02:00
__JS_ATOM_NULL = JS_ATOM_NULL,
2020-09-06 18:53:08 +02:00
#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,
2020-11-08 14:30:56 +01:00
JSValue val, BOOL is_array_ctor);
2020-09-06 18:53:08 +02:00
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);
2020-09-06 18:53:08 +02:00
static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt);
static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p);
static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p);
static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val);
static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val);
2020-09-06 18:53:08 +02:00
static __maybe_unused void JS_DumpShapes(JSRuntime *rt);
static void js_dump_value_write(void *opaque, const char *buf, size_t len);
2020-09-06 18:53:08 +02:00
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);
2020-09-06 18:53:08 +02:00
static void js_object_data_finalizer(JSRuntime *rt, JSValue val);
static void js_object_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func);
2020-09-06 19:04:20 +02:00
static void js_c_function_finalizer(JSRuntime *rt, JSValue val);
static void js_c_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func);
2020-09-06 18:53:08 +02:00
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);
#define HINT_STRING 0
#define HINT_NUMBER 1
#define HINT_NONE 2
#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive
static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint);
2020-09-06 18:53:08 +02:00
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_new_string8_len(JSContext *ctx, const char *buf, int len);
2020-09-06 18:53:08 +02:00
static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
JSValueConst flags);
static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor,
JSValue pattern, JSValue bc);
static void gc_decref(JSRuntime *rt);
static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
const JSClassDef *class_def, JSAtom name);
typedef enum JSStrictEqModeEnum {
JS_EQ_STRICT,
JS_EQ_SAME_VALUE,
JS_EQ_SAME_VALUE_ZERO,
} JSStrictEqModeEnum;
static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2,
JSStrictEqModeEnum eq_mode);
static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2);
2020-09-06 18:53:08 +02:00
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);
static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val);
2020-09-06 18:53:08 +02:00
JSValue JS_ThrowOutOfMemory(JSContext *ctx);
static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx);
static int js_resolve_proxy(JSContext *ctx, JSValueConst *pval, int throw_exception);
2020-09-06 18:53:08 +02:00
static int JS_CreateProperty(JSContext *ctx, JSObject *p,
JSAtom prop, JSValueConst val,
JSValueConst getter, JSValueConst setter,
int flags);
static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2,
int pos2, int len);
2020-09-06 19:07:30 +02:00
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);
static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj);
static JSValue js_typed_array_constructor(JSContext *ctx,
JSValueConst this_val,
int argc, JSValueConst *argv,
int classid);
static JSValue js_typed_array_constructor_ta(JSContext *ctx,
JSValueConst new_target,
JSValueConst src_obj,
int classid);
2020-09-06 18:53:08 +02:00
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 void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s);
static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s);
2020-09-06 18:53:08 +02:00
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);
2020-09-06 19:04:20 +02:00
static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
JS_MarkFunc *mark_func);
2020-09-06 18:53:08 +02:00
static JSValue js_import_meta(JSContext *ctx);
static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier, JSValueConst options);
2020-09-06 18:53:08 +02:00
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 JSValue js_promise_then(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv);
2020-09-06 18:53:08 +02:00
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 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);
2020-11-08 14:30:56 +01:00
static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque);
static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom,
2020-09-06 19:04:20 +02:00
void *opaque);
2020-11-08 14:30:56 +01:00
static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p,
JSAtom atom, void *opaque);
static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int is_map);
static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh);
static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh);
static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh);
static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects);
static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen,
JSValueConst obj, JSValueConst method);
static int js_string_find_invalid_codepoint(JSString *p);
static JSValue js_regexp_toString(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv);
static JSValue get_date_string(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic);
static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv);
2020-09-06 18:53:08 +02:00
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);
}
/* 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));
}
2020-09-06 19:07:30 +02:00
static no_inline int js_realloc_array(JSContext *ctx, void **parray,
int elem_size, int *psize, int req_size)
{
int new_size;
size_t slack;
void *new_array;
/* XXX: potential arithmetic overflow */
new_size = max_int(req_size, *psize * 3 / 2);
new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack);
if (!new_array)
return -1;
new_size += slack / elem_size;
*psize = new_size;
*parray = new_array;
return 0;
}
/* resize the array and update its size if req_size > *psize */
static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size,
int *psize, int req_size)
{
if (unlikely(req_size > *psize))
return js_realloc_array(ctx, parray, elem_size, psize, req_size);
else
return 0;
}
2020-09-06 18:53:08 +02:00
static inline void js_dbuf_init(JSContext *ctx, DynBuf *s)
{
dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt);
}
static inline int is_digit(int c) {
return c >= '0' && c <= '9';
}
static inline int string_get(const JSString *p, int idx) {
return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx];
}
2020-09-06 18:53:08 +02:00
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 */
2020-09-06 19:07:30 +02:00
{ JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */
2020-09-06 18:53:08 +02:00
{ 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 */
2020-09-06 19:04:20 +02:00
{ JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */
2020-09-06 18:53:08 +02:00
{ 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 */
{ 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 */
{ JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */
2020-09-06 18:53:08 +02:00
{ 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 */
{ JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */
{ 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 */
2020-09-06 19:07:30 +02:00
{ JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */
2020-09-06 18:53:08 +02:00
{ 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;
}
2020-09-06 19:07:30 +02:00
#if !defined(CONFIG_STACK_CHECK)
/* no stack limitation */
2021-03-27 11:17:31 +01:00
static inline uintptr_t js_get_stack_pointer(void)
2020-09-06 19:04:20 +02:00
{
2021-03-27 11:17:31 +01:00
return 0;
2020-09-06 19:04:20 +02:00
}
static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size)
{
return FALSE;
}
#else
/* Note: OS and CPU dependent */
2021-03-27 11:17:31 +01:00
static inline uintptr_t js_get_stack_pointer(void)
2020-09-06 19:04:20 +02:00
{
2021-03-27 11:17:31 +01:00
return (uintptr_t)__builtin_frame_address(0);
2020-09-06 19:04:20 +02:00
}
static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size)
{
2021-03-27 11:17:31 +01:00
uintptr_t sp;
sp = js_get_stack_pointer() - alloca_size;
return unlikely(sp < rt->stack_limit);
2020-09-06 19:04:20 +02:00
}
#endif
2020-09-06 18:53:08 +02:00
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
{
JSRuntime *rt;
JSMallocState ms;
memset(&ms, 0, sizeof(ms));
ms.opaque = opaque;
ms.malloc_limit = -1;
rt = mf->js_malloc(&ms, sizeof(JSRuntime));
if (!rt)
return NULL;
memset(rt, 0, sizeof(*rt));
rt->mf = *mf;
if (!rt->mf.js_malloc_usable_size) {
/* use dummy function if none provided */
rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown;
}
rt->malloc_state = ms;
rt->malloc_gc_threshold = 256 * 1024;
init_list_head(&rt->context_list);
init_list_head(&rt->gc_obj_list);
init_list_head(&rt->gc_zero_ref_count_list);
rt->gc_phase = JS_GC_PHASE_NONE;
init_list_head(&rt->weakref_list);
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
#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;
2020-09-06 19:04:20 +02:00
rt->stack_size = JS_DEFAULT_STACK_SIZE;
2021-03-27 11:17:31 +01:00
JS_UpdateStackTop(rt);
rt->current_exception = JS_UNINITIALIZED;
2020-09-06 19:04:20 +02:00
2020-09-06 18:53:08 +02:00
return rt;
fail:
JS_FreeRuntime(rt);
return NULL;
}
2020-09-06 19:02:03 +02:00
void *JS_GetRuntimeOpaque(JSRuntime *rt)
{
return rt->user_opaque;
}
void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque)
{
rt->user_opaque = opaque;
}
2020-09-06 18:53:08 +02:00
/* default memory allocation functions with memory limitation */
2024-01-11 15:29:19 +01:00
static size_t js_def_malloc_usable_size(const void *ptr)
2020-09-06 18:53:08 +02:00
{
#if defined(__APPLE__)
return malloc_size(ptr);
#elif defined(_WIN32)
2024-01-11 15:29:19 +01:00
return _msize((void *)ptr);
2020-09-06 18:53:08 +02:00
#elif defined(EMSCRIPTEN)
return 0;
#elif defined(__linux__) || defined(__GLIBC__)
2024-01-11 15:29:19 +01:00
return malloc_usable_size((void *)ptr);
2020-09-06 18:53:08 +02:00
#else
/* change this to `return 0;` if compilation fails */
2024-01-11 15:29:19 +01:00
return malloc_usable_size((void *)ptr);
2020-09-06 18:53:08 +02:00
#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,
2024-01-11 15:29:19 +01:00
js_def_malloc_usable_size,
2020-09-06 18:53:08 +02:00
};
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;
}
2020-09-06 19:07:30 +02:00
void JS_SetSharedArrayBufferFunctions(JSRuntime *rt,
const JSSharedArrayBufferFunctions *sf)
{
rt->sab_funcs = *sf;
}
void JS_SetStripInfo(JSRuntime *rt, int flags)
{
rt->strip_flags = flags;
}
int JS_GetStripInfo(JSRuntime *rt)
{
return rt->strip_flags;
}
2020-09-06 18:53:08 +02:00
/* 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;
2020-09-06 19:04:20 +02:00
JS_FreeValueRT(rt, rt->current_exception);
2020-09-06 18:53:08 +02:00
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);
/* don't remove the weak objects to avoid create new jobs with
FinalizationRegistry */
JS_RunGCInternal(rt, FALSE);
2020-09-06 18:53:08 +02:00
#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));
assert(list_empty(&rt->weakref_list));
2020-09-06 18:53:08 +02:00
/* free the classes */
for(i = 0; i < rt->class_count; i++) {
JSClass *cl = &rt->class_array[i];
if (cl->class_id != 0) {
JS_FreeAtomRT(rt, cl->class_name);
}
}
js_free_rt(rt, rt->class_array);
#ifdef DUMP_LEAKS
/* only the atoms defined in JS_InitAtoms() should be left */
{
BOOL header_done = FALSE;
for(i = 0; i < rt->atom_size; i++) {
JSAtomStruct *p = rt->atom_array[i];
if (!atom_is_free(p) /* && p->str*/) {
if (i >= JS_ATOM_END || p->header.ref_count != 1) {
if (!header_done) {
header_done = TRUE;
if (rt->rt_info) {
printf("%s:1: atom leakage:", rt->rt_info);
} else {
printf("Atom leaks:\n"
" %6s %6s %s\n",
"ID", "REFCNT", "NAME");
}
}
if (rt->rt_info) {
printf(" ");
} else {
printf(" %6u %6u ", i, p->header.ref_count);
}
switch (p->atom_type) {
case JS_ATOM_TYPE_STRING:
JS_DumpString(rt, p);
break;
case JS_ATOM_TYPE_GLOBAL_SYMBOL:
printf("Symbol.for(");
JS_DumpString(rt, p);
printf(")");
break;
case JS_ATOM_TYPE_SYMBOL:
if (p->hash != JS_ATOM_HASH_PRIVATE) {
2020-09-06 18:53:08 +02:00
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);
}
}
JSContext *JS_NewContextRaw(JSRuntime *rt)
{
JSContext *ctx;
int i;
ctx = js_mallocz_rt(rt, sizeof(JSContext));
if (!ctx)
return NULL;
2020-09-06 19:04:20 +02:00
ctx->header.ref_count = 1;
add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT);
2020-09-06 18:53:08 +02:00
ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) *
rt->class_count);
if (!ctx->class_proto) {
js_free_rt(rt, ctx);
return NULL;
}
ctx->rt = rt;
list_add_tail(&ctx->link, &rt->context_list);
for(i = 0; i < rt->class_count; i++)
ctx->class_proto[i] = JS_NULL;
2020-09-06 19:04:20 +02:00
ctx->array_ctor = JS_NULL;
2020-09-06 18:53:08 +02:00
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);
JS_AddIntrinsicWeakRef(ctx);
2020-09-06 18:53:08 +02:00
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,
} 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)) {
2020-09-06 18:53:08 +02:00
js_free_module_def(ctx, m);
}
}
}
2020-09-06 19:04:20 +02:00
JSContext *JS_DupContext(JSContext *ctx)
{
ctx->header.ref_count++;
return ctx;
}
/* used by the GC */
static void JS_MarkContext(JSRuntime *rt, JSContext *ctx,
JS_MarkFunc *mark_func)
{
int i;
struct list_head *el;
/* modules are not seen by the GC, so we directly mark the objects
referenced by each module */
list_for_each(el, &ctx->loaded_modules) {
JSModuleDef *m = list_entry(el, JSModuleDef, link);
js_mark_module_def(rt, m, mark_func);
}
JS_MarkValue(rt, ctx->global_obj, mark_func);
JS_MarkValue(rt, ctx->global_var_obj, mark_func);
JS_MarkValue(rt, ctx->throw_type_error, mark_func);
JS_MarkValue(rt, ctx->eval_obj, mark_func);
JS_MarkValue(rt, ctx->array_proto_values, mark_func);
for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) {
JS_MarkValue(rt, ctx->native_error_proto[i], mark_func);
}
for(i = 0; i < rt->class_count; i++) {
JS_MarkValue(rt, ctx->class_proto[i], mark_func);
}
JS_MarkValue(rt, ctx->iterator_proto, mark_func);
JS_MarkValue(rt, ctx->async_iterator_proto, mark_func);
JS_MarkValue(rt, ctx->promise_ctor, mark_func);
JS_MarkValue(rt, ctx->array_ctor, mark_func);
JS_MarkValue(rt, ctx->regexp_ctor, mark_func);
JS_MarkValue(rt, ctx->function_ctor, mark_func);
JS_MarkValue(rt, ctx->function_proto, mark_func);
if (ctx->array_shape)
mark_func(rt, &ctx->array_shape->header);
}
2020-09-06 18:53:08 +02:00
void JS_FreeContext(JSContext *ctx)
{
JSRuntime *rt = ctx->rt;
int i;
2020-09-06 19:04:20 +02:00
if (--ctx->header.ref_count > 0)
return;
assert(ctx->header.ref_count == 0);
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
#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->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);
2020-09-06 19:04:20 +02:00
JS_FreeValue(ctx, ctx->array_ctor);
2020-09-06 18:53:08 +02:00
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);
2020-09-06 19:04:20 +02:00
remove_gc_object(&ctx->header);
2020-09-06 18:53:08 +02:00
js_free_rt(ctx->rt, ctx);
}
JSRuntime *JS_GetRuntime(JSContext *ctx)
{
return ctx->rt;
}
2021-03-27 11:17:31 +01:00
static void update_stack_limit(JSRuntime *rt)
{
if (rt->stack_size == 0) {
rt->stack_limit = 0; /* no limit */
} else {
rt->stack_limit = rt->stack_top - rt->stack_size;
}
}
2020-09-06 19:04:20 +02:00
void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size)
2020-09-06 18:53:08 +02:00
{
2020-09-06 19:04:20 +02:00
rt->stack_size = stack_size;
2021-03-27 11:17:31 +01:00
update_stack_limit(rt);
}
void JS_UpdateStackTop(JSRuntime *rt)
{
rt->stack_top = js_get_stack_pointer();
update_stack_limit(rt);
2020-09-06 18:53:08 +02:00
}
static inline BOOL is_strict_mode(JSContext *ctx)
{
2020-09-06 19:04:20 +02:00
JSStackFrame *sf = ctx->rt->current_stack_frame;
2020-09-06 18:53:08 +02:00
return (sf && (sf->js_mode & JS_MODE_STRICT));
}
/* JSAtom support */
#define JS_ATOM_TAG_INT (1U << 31)
#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1)
#define JS_ATOM_MAX ((1U << 30) - 1)
/* return the max count from the hash size */
#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2)
static inline BOOL __JS_AtomIsConst(JSAtom v)
{
#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1
return (int32_t)v <= 0;
#else
return (int32_t)v < JS_ATOM_END;
#endif
}
static inline BOOL __JS_AtomIsTaggedInt(JSAtom v)
{
return (v & JS_ATOM_TAG_INT) != 0;
}
static inline JSAtom __JS_AtomFromUInt32(uint32_t v)
{
return v | JS_ATOM_TAG_INT;
}
static inline uint32_t __JS_AtomToUInt32(JSAtom atom)
{
return atom & ~JS_ATOM_TAG_INT;
}
static inline int is_num(int c)
{
return c >= '0' && c <= '9';
}
/* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */
static inline BOOL is_num_string(uint32_t *pval, const JSString *p)
{
uint32_t n;
uint64_t n64;
int c, i, len;
len = p->len;
if (len == 0 || len > 10)
return FALSE;
c = string_get(p, 0);
2020-09-06 18:53:08 +02:00
if (is_num(c)) {
if (c == '0') {
if (len != 1)
return FALSE;
n = 0;
} else {
n = c - '0';
for(i = 1; i < len; i++) {
c = string_get(p, i);
2020-09-06 18:53:08 +02:00
if (!is_num(c))
return FALSE;
n64 = (uint64_t)n * 10 + (c - '0');
if ((n64 >> 32) != 0)
return FALSE;
n = n64;
}
}
*pval = n;
return TRUE;
} else {
return FALSE;
}
}
/* XXX: could use faster version ? */
static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h)
{
size_t i;
for(i = 0; i < len; i++)
h = h * 263 + str[i];
return h;
}
static inline uint32_t hash_string16(const uint16_t *str,
size_t len, uint32_t h)
{
size_t i;
for(i = 0; i < len; i++)
h = h * 263 + str[i];
return h;
}
static uint32_t hash_string(const JSString *str, uint32_t h)
{
if (str->is_wide_char)
h = hash_string16(str->u.str16, str->len, h);
else
h = hash_string8(str->u.str8, str->len, h);
return h;
}
static uint32_t hash_string_rope(JSValueConst val, uint32_t h)
{
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) {
return hash_string(JS_VALUE_GET_STRING(val), h);
} else {
JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val);
h = hash_string_rope(r->left, h);
return hash_string_rope(r->right, h);
}
}
static __maybe_unused void JS_DumpChar(FILE *fo, int c, int sep)
{
if (c == sep || c == '\\') {
fputc('\\', fo);
fputc(c, fo);
} else if (c >= ' ' && c <= 126) {
fputc(c, fo);
} else if (c == '\n') {
fputc('\\', fo);
fputc('n', fo);
} else {
fprintf(fo, "\\u%04x", c);
}
}
static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p)
2020-09-06 18:53:08 +02:00
{
int i, sep;
2020-09-06 18:53:08 +02:00
if (p == NULL) {
printf("<null>");
return;
}
printf("%d", p->header.ref_count);
sep = (p->header.ref_count == 1) ? '\"' : '\'';
putchar(sep);
for(i = 0; i < p->len; i++) {
JS_DumpChar(stdout, string_get(p, i), sep);
2020-09-06 18:53:08 +02:00
}
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:
if (p->hash == JS_ATOM_HASH_PRIVATE)
2020-09-06 18:53:08 +02:00
return JS_ATOM_KIND_PRIVATE;
else
return JS_ATOM_KIND_SYMBOL;
2020-09-06 18:53:08 +02:00
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, 0, str, 0, len) == 0) {
2020-09-06 18:53:08 +02:00
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 = 0;
2020-09-06 18:53:08 +02:00
} 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);
}
/* Warning: str must be ASCII only */
2020-09-06 18:53:08 +02:00
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
if (p->atom_type == JS_ATOM_TYPE_SYMBOL &&
p->hash != JS_ATOM_HASH_PRIVATE && p->hash != 0) {
/* live weak references are still present on this object: keep
it */
} else {
js_free_rt(rt, p);
}
2020-09-06 18:53:08 +02:00
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);
}
/* XXX: optimize */
static size_t count_ascii(const uint8_t *buf, size_t len)
{
const uint8_t *p, *p_end;
p = buf;
p_end = buf + len;
while (p < p_end && *p < 128)
p++;
return p - buf;
}
/* str is UTF-8 encoded */
2020-09-06 18:53:08 +02:00
JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len)
{
JSValue val;
if (len == 0 ||
(!is_digit(*str) &&
count_ascii((const uint8_t *)str, len) == len)) {
2025-04-10 17:38:44 +02:00
JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING);
2020-09-06 18:53:08 +02:00
if (atom)
return atom;
}
val = JS_NewStringLen(ctx, str, len);
if (JS_IsException(val))
return JS_ATOM_NULL;
return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val));
}
JSAtom JS_NewAtom(JSContext *ctx, const char *str)
{
return JS_NewAtomLen(ctx, str, strlen(str));
}
JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n)
{
if (n <= JS_ATOM_MAX_INT) {
return __JS_AtomFromUInt32(n);
} else {
char buf[11];
JSValue val;
size_t len;
len = u32toa(buf, n);
val = js_new_string8_len(ctx, buf, len);
2020-09-06 18:53:08 +02:00
if (JS_IsException(val))
return JS_ATOM_NULL;
return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val),
JS_ATOM_TYPE_STRING);
}
}
static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n)
{
if ((uint64_t)n <= JS_ATOM_MAX_INT) {
return __JS_AtomFromUInt32((uint32_t)n);
} else {
char buf[24];
JSValue val;
size_t len;
len = i64toa(buf, n);
val = js_new_string8_len(ctx, buf, len);
2020-09-06 18:53:08 +02:00
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, "<null>");
} else {
int i, c;
char *q;
JSString *str;
q = buf;
p = rt->atom_array[atom];
assert(!atom_is_free(p));
str = p;
if (str) {
if (!str->is_wide_char) {
/* special case ASCII strings */
c = 0;
for(i = 0; i < str->len; i++) {
c |= str->u.str8[i];
}
if (c < 0x80)
return (const char *)str->u.str8;
}
for(i = 0; i < str->len; i++) {
c = string_get(str, i);
2020-09-06 18:53:08 +02:00
if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX)
break;
if (c < 128) {
*q++ = c;
} else {
q += unicode_to_utf8((uint8_t *)q, c);
}
}
}
*q = '\0';
}
}
return buf;
}
static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom)
{
return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom);
}
static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string)
{
char buf[ATOM_GET_STR_BUF_SIZE];
if (__JS_AtomIsTaggedInt(atom)) {
size_t len = u32toa(buf, __JS_AtomToUInt32(atom));
return js_new_string8_len(ctx, buf, len);
2020-09-06 18:53:08 +02:00
} 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, ret;
2020-09-06 18:53:08 +02:00
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;
switch(atom) {
case JS_ATOM_minus_zero:
return __JS_NewFloat64(ctx, -0.0);
case JS_ATOM_Infinity:
return __JS_NewFloat64(ctx, INFINITY);
case JS_ATOM_minus_Infinity:
return __JS_NewFloat64(ctx, -INFINITY);
case JS_ATOM_NaN:
return __JS_NewFloat64(ctx, NAN);
default:
break;
2020-09-06 18:53:08 +02:00
}
p = p1;
if (p->len == 0)
return JS_UNDEFINED;
c = string_get(p, 0);
if (!is_num(c) && c != '-')
return JS_UNDEFINED;
2020-09-06 18:53:08 +02:00
/* 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_PRIVATE) ||
2020-09-06 18:53:08 +02:00
p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) &&
!(p->len == 0 && p->is_wide_char != 0));
}
/* free with JS_FreeCString() */
2025-05-20 18:03:29 +02:00
const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom)
2020-09-06 18:53:08 +02:00
{
JSValue str;
const char *cstr;
str = JS_AtomToString(ctx, atom);
2025-05-20 18:03:29 +02:00
if (JS_IsException(str)) {
if (plen)
*plen = 0;
2020-09-06 18:53:08 +02:00
return NULL;
2025-05-20 18:03:29 +02:00
}
cstr = JS_ToCStringLen(ctx, plen, str);
2020-09-06 18:53:08 +02:00
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;
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
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];
size_t len;
len = u32toa(buf, n);
buf[len] = '\0';
2020-09-06 18:53:08 +02:00
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 */
2024-01-02 16:08:48 +01:00
#ifdef CONFIG_ATOMICS
static pthread_mutex_t js_class_id_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
2020-09-06 18:53:08 +02:00
/* a new class ID is allocated if *pclass_id != 0 */
JSClassID JS_NewClassID(JSClassID *pclass_id)
{
JSClassID class_id;
2024-01-02 16:08:48 +01:00
#ifdef CONFIG_ATOMICS
pthread_mutex_lock(&js_class_id_mutex);
#endif
2020-09-06 18:53:08 +02:00
class_id = *pclass_id;
if (class_id == 0) {
class_id = js_class_id_alloc++;
*pclass_id = class_id;
}
2024-01-02 16:08:48 +01:00
#ifdef CONFIG_ATOMICS
pthread_mutex_unlock(&js_class_id_mutex);
#endif
2020-09-06 18:53:08 +02:00
return class_id;
}
JSClassID JS_GetClassID(JSValue v)
{
JSObject *p;
if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT)
return JS_INVALID_CLASS_ID;
p = JS_VALUE_GET_OBJ(v);
return p->class_id;
}
2020-09-06 18:53:08 +02:00
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;
2021-03-27 11:17:31 +01:00
if (class_id >= (1 << 16))
return -1;
2020-09-06 18:53:08 +02:00
if (class_id < rt->class_count &&
rt->class_array[class_id].class_id != 0)
return -1;
if (class_id >= rt->class_count) {
new_size = max_int(JS_CLASS_INIT_COUNT,
max_int(class_id + 1, rt->class_count * 3 / 2));
/* reallocate the context class prototype array, if any */
list_for_each(el, &rt->context_list) {
JSContext *ctx = list_entry(el, JSContext, link);
JSValue *new_tab;
new_tab = js_realloc_rt(rt, ctx->class_proto,
sizeof(ctx->class_proto[0]) * new_size);
if (!new_tab)
return -1;
for(i = rt->class_count; i < new_size; i++)
new_tab[i] = JS_NULL;
ctx->class_proto = new_tab;
}
/* reallocate the class array */
new_class_array = js_realloc_rt(rt, rt->class_array,
sizeof(JSClass) * new_size);
if (!new_class_array)
return -1;
memset(new_class_array + rt->class_count, 0,
(new_size - rt->class_count) * sizeof(JSClass));
rt->class_array = new_class_array;
rt->class_count = new_size;
}
cl = &rt->class_array[class_id];
cl->class_id = class_id;
cl->class_name = JS_DupAtomRT(rt, name);
cl->finalizer = class_def->finalizer;
cl->gc_mark = class_def->gc_mark;
cl->call = class_def->call;
cl->exotic = class_def->exotic;
return 0;
}
int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def)
{
int ret, len;
JSAtom name;
len = strlen(class_def->class_name);
name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING);
if (name == JS_ATOM_NULL) {
name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING);
if (name == JS_ATOM_NULL)
return -1;
}
ret = JS_NewClass1(rt, class_id, class_def, name);
JS_FreeAtomRT(rt, name);
return ret;
}
static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len)
2020-09-06 18:53:08 +02:00
{
JSString *str;
if (len <= 0) {
return JS_AtomToString(ctx, JS_ATOM_empty_string);
}
str = js_alloc_string(ctx, len, 0);
if (!str)
return JS_EXCEPTION;
memcpy(str->u.str8, buf, len);
str->u.str8[len] = '\0';
return JS_MKPTR(JS_TAG_STRING, str);
}
static JSValue js_new_string8(JSContext *ctx, const char *buf)
{
return js_new_string8_len(ctx, buf, strlen(buf));
}
static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len)
2020-09-06 18:53:08 +02:00
{
JSString *str;
str = js_alloc_string(ctx, len, 1);
if (!str)
return JS_EXCEPTION;
memcpy(str->u.str16, buf, len * 2);
return JS_MKPTR(JS_TAG_STRING, str);
}
static JSValue js_new_string_char(JSContext *ctx, uint16_t c)
{
if (c < 0x100) {
uint8_t ch8 = c;
return js_new_string8_len(ctx, (const char *)&ch8, 1);
2020-09-06 18:53:08 +02:00
} else {
uint16_t ch16 = c;
return js_new_string16_len(ctx, &ch16, 1);
2020-09-06 18:53:08 +02:00
}
}
static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end)
{
int len = end - start;
if (start == 0 && end == p->len) {
return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
}
if (p->is_wide_char && len > 0) {
JSString *str;
int i;
uint16_t c = 0;
for (i = start; i < end; i++) {
c |= p->u.str16[i];
}
if (c > 0xFF)
return js_new_string16_len(ctx, p->u.str16 + start, len);
2020-09-06 18:53:08 +02:00
str = js_alloc_string(ctx, len, 0);
if (!str)
return JS_EXCEPTION;
for (i = 0; i < len; i++) {
str->u.str8[i] = p->u.str16[start + i];
}
str->u.str8[len] = '\0';
return JS_MKPTR(JS_TAG_STRING, str);
} else {
return js_new_string8_len(ctx, (const char *)(p->u.str8 + start), len);
2020-09-06 18:53:08 +02:00
}
}
typedef struct StringBuffer {
JSContext *ctx;
JSString *str;
int len;
int size;
int is_wide_char;
int error_status;
} StringBuffer;
/* It is valid to call string_buffer_end() and all string_buffer functions even
if string_buffer_init() or another string_buffer function returns an error.
If the error_status is set, string_buffer_end() returns JS_EXCEPTION.
*/
static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size,
int is_wide)
{
s->ctx = ctx;
s->size = size;
s->len = 0;
s->is_wide_char = is_wide;
s->error_status = 0;
s->str = js_alloc_string(ctx, size, is_wide);
if (unlikely(!s->str)) {
s->size = 0;
return s->error_status = -1;
}
#ifdef DUMP_LEAKS
/* the StringBuffer may reallocate the JSString, only link it at the end */
list_del(&s->str->link);
#endif
return 0;
}
static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size)
{
return string_buffer_init2(ctx, s, size, 0);
}
static void string_buffer_free(StringBuffer *s)
{
js_free(s->ctx, s->str);
s->str = NULL;
}
static int string_buffer_set_error(StringBuffer *s)
{
js_free(s->ctx, s->str);
s->str = NULL;
s->size = 0;
s->len = 0;
return s->error_status = -1;
}
static no_inline int string_buffer_widen(StringBuffer *s, int size)
{
JSString *str;
size_t slack;
int i;
if (s->error_status)
return -1;
str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack);
if (!str)
return string_buffer_set_error(s);
size += slack >> 1;
for(i = s->len; i-- > 0;) {
str->u.str16[i] = str->u.str8[i];
}
s->is_wide_char = 1;
s->size = size;
s->str = str;
return 0;
}
static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c)
{
JSString *new_str;
int new_size;
size_t new_size_bytes, slack;
if (s->error_status)
return -1;
if (new_len > JS_STRING_LEN_MAX) {
JS_ThrowInternalError(s->ctx, "string too long");
return string_buffer_set_error(s);
}
new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX);
if (!s->is_wide_char && c >= 0x100) {
return string_buffer_widen(s, new_size);
}
new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char;
new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack);
if (!new_str)
return string_buffer_set_error(s);
new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX);
s->size = new_size;
s->str = new_str;
return 0;
}
static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c)
{
if (unlikely(s->len >= s->size)) {
if (string_buffer_realloc(s, s->len + 1, c))
return -1;
}
if (s->is_wide_char) {
s->str->u.str16[s->len++] = c;
} else if (c < 0x100) {
s->str->u.str8[s->len++] = c;
} else {
if (string_buffer_widen(s, s->size))
return -1;
s->str->u.str16[s->len++] = c;
}
return 0;
}
/* 0 <= c <= 0xff */
static int string_buffer_putc8(StringBuffer *s, uint32_t c)
{
if (unlikely(s->len >= s->size)) {
if (string_buffer_realloc(s, s->len + 1, c))
return -1;
}
if (s->is_wide_char) {
s->str->u.str16[s->len++] = c;
} else {
s->str->u.str8[s->len++] = c;
}
return 0;
}
/* 0 <= c <= 0xffff */
static int string_buffer_putc16(StringBuffer *s, uint32_t c)
{
if (likely(s->len < s->size)) {
if (s->is_wide_char) {
s->str->u.str16[s->len++] = c;
return 0;
} else if (c < 0x100) {
s->str->u.str8[s->len++] = c;
return 0;
}
}
return string_buffer_putc_slow(s, c);
}
/* 0 <= c <= 0x10ffff */
static int string_buffer_putc(StringBuffer *s, uint32_t c)
{
if (unlikely(c >= 0x10000)) {
/* surrogate pair */
if (string_buffer_putc16(s, get_hi_surrogate(c)))
2020-09-06 18:53:08 +02:00
return -1;
c = get_lo_surrogate(c);
2020-09-06 18:53:08 +02:00
}
return string_buffer_putc16(s, c);
}
static int string_getc(const JSString *p, int *pidx)
{
int idx, c, c1;
idx = *pidx;
if (p->is_wide_char) {
c = p->u.str16[idx++];
if (is_hi_surrogate(c) && idx < p->len) {
2020-09-06 18:53:08 +02:00
c1 = p->u.str16[idx];
if (is_lo_surrogate(c1)) {
c = from_surrogate(c, c1);
2020-09-06 18:53:08 +02:00
idx++;
}
}
} else {
c = p->u.str8[idx++];
}
*pidx = idx;
return c;
}
static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len)
{
int i;
if (s->len + len > s->size) {
if (string_buffer_realloc(s, s->len + len, 0))
return -1;
}
if (s->is_wide_char) {
for (i = 0; i < len; i++) {
s->str->u.str16[s->len + i] = p[i];
}
s->len += len;
} else {
memcpy(&s->str->u.str8[s->len], p, len);
s->len += len;
}
return 0;
}
static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len)
{
int c = 0, i;
for (i = 0; i < len; i++) {
c |= p[i];
}
if (s->len + len > s->size) {
if (string_buffer_realloc(s, s->len + len, c))
return -1;
} else if (!s->is_wide_char && c >= 0x100) {
if (string_buffer_widen(s, s->size))
return -1;
}
if (s->is_wide_char) {
memcpy(&s->str->u.str16[s->len], p, len << 1);
s->len += len;
} else {
for (i = 0; i < len; i++) {
s->str->u.str8[s->len + i] = p[i];
}
s->len += len;
}
return 0;
}
/* appending an ASCII string */
static int string_buffer_puts8(StringBuffer *s, const char *str)
{
return string_buffer_write8(s, (const uint8_t *)str, strlen(str));
}
static int string_buffer_concat(StringBuffer *s, const JSString *p,
uint32_t from, uint32_t to)
{
if (to <= from)
return 0;
if (p->is_wide_char)
return string_buffer_write16(s, p->u.str16 + from, to - from);
else
return string_buffer_write8(s, p->u.str8 + from, to - from);
}
static int string_buffer_concat_value(StringBuffer *s, JSValueConst v)
{
JSString *p;
JSValue v1;
int res;
if (s->error_status) {
/* prevent exception overload */
return -1;
}
if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) {
if (JS_VALUE_GET_TAG(v) == JS_TAG_STRING_ROPE) {
JSStringRope *r = JS_VALUE_GET_STRING_ROPE(v);
/* recursion is acceptable because the rope depth is bounded */
if (string_buffer_concat_value(s, r->left))
return -1;
return string_buffer_concat_value(s, r->right);
} else {
v1 = JS_ToString(s->ctx, v);
if (JS_IsException(v1))
return string_buffer_set_error(s);
p = JS_VALUE_GET_STRING(v1);
res = string_buffer_concat(s, p, 0, p->len);
JS_FreeValue(s->ctx, v1);
return res;
}
2020-09-06 18:53:08 +02:00
}
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;
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
p_start = (const uint8_t *)buf;
p_end = p_start + buf_len;
len1 = count_ascii(p_start, buf_len);
p = p_start + len1;
2020-09-06 18:53:08 +02:00
if (len1 > JS_STRING_LEN_MAX)
return JS_ThrowInternalError(ctx, "string too long");
if (p == p_end) {
/* ASCII string */
return js_new_string8_len(ctx, buf, buf_len);
2020-09-06 18:53:08 +02:00
} else {
if (string_buffer_init(ctx, b, buf_len))
goto fail;
string_buffer_write8(b, p_start, len1);
while (p < p_end) {
if (*p < 128) {
string_buffer_putc8(b, *p++);
} else {
/* parse utf-8 sequence, return 0xFFFFFFFF for error */
c = unicode_from_utf8(p, p_end - p, &p_next);
if (c < 0x10000) {
p = p_next;
} else if (c <= 0x10FFFF) {
p = p_next;
/* surrogate pair */
string_buffer_putc16(b, get_hi_surrogate(c));
c = get_lo_surrogate(c);
2020-09-06 18:53:08 +02:00
} else {
/* invalid char */
c = 0xfffd;
/* skip the invalid chars */
/* XXX: seems incorrect. Why not just use c = *p++; ? */
while (p < p_end && (*p >= 0x80 && *p < 0xc0))
p++;
if (p < p_end) {
p++;
while (p < p_end && (*p >= 0x80 && *p < 0xc0))
p++;
}
}
string_buffer_putc16(b, c);
}
}
}
return string_buffer_end(b);
fail:
string_buffer_free(b);
return JS_EXCEPTION;
}
static JSValue JS_ConcatString3(JSContext *ctx, const char *str1,
JSValue str2, const char *str3)
{
StringBuffer b_s, *b = &b_s;
int len1, len3;
JSString *p;
if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) {
str2 = JS_ToStringFree(ctx, str2);
if (JS_IsException(str2))
goto fail;
}
p = JS_VALUE_GET_STRING(str2);
len1 = strlen(str1);
len3 = strlen(str3);
if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char))
goto fail;
string_buffer_write8(b, (const uint8_t *)str1, len1);
string_buffer_concat(b, p, 0, p->len);
string_buffer_write8(b, (const uint8_t *)str3, len3);
JS_FreeValue(ctx, str2);
return string_buffer_end(b);
fail:
JS_FreeValue(ctx, str2);
return JS_EXCEPTION;
}
JSValue JS_NewAtomString(JSContext *ctx, const char *str)
{
JSAtom atom = JS_NewAtom(ctx, str);
if (atom == JS_ATOM_NULL)
return JS_EXCEPTION;
JSValue val = JS_AtomToString(ctx, atom);
JS_FreeAtom(ctx, atom);
return val;
}
/* return (NULL, 0) if exception. */
/* return pointer into a JSString with a live ref_count */
/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */
const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, BOOL cesu8)
{
JSValue val;
JSString *str, *str_new;
int pos, len, c, c1;
uint8_t *q;
if (JS_VALUE_GET_TAG(val1) != JS_TAG_STRING) {
val = JS_ToString(ctx, val1);
if (JS_IsException(val))
goto fail;
} else {
val = JS_DupValue(ctx, val1);
}
str = JS_VALUE_GET_STRING(val);
len = str->len;
if (!str->is_wide_char) {
const uint8_t *src = str->u.str8;
int count;
/* count the number of non-ASCII characters */
/* Scanning the whole string is required for ASCII strings,
and computing the number of non-ASCII bytes is less expensive
than testing each byte, hence this method is faster for ASCII
strings, which is the most common case.
*/
count = 0;
for (pos = 0; pos < len; pos++) {
count += src[pos] >> 7;
}
if (count == 0) {
if (plen)
*plen = len;
return (const char *)src;
}
str_new = js_alloc_string(ctx, len + count, 0);
if (!str_new)
goto fail;
q = str_new->u.str8;
for (pos = 0; pos < len; pos++) {
c = src[pos];
if (c < 0x80) {
*q++ = c;
} else {
*q++ = (c >> 6) | 0xc0;
*q++ = (c & 0x3f) | 0x80;
}
}
} else {
const uint16_t *src = str->u.str16;
/* Allocate 3 bytes per 16 bit code point. Surrogate pairs may
produce 4 bytes but use 2 code points.
*/
str_new = js_alloc_string(ctx, len * 3, 0);
if (!str_new)
goto fail;
q = str_new->u.str8;
pos = 0;
while (pos < len) {
c = src[pos++];
if (c < 0x80) {
*q++ = c;
} else {
if (is_hi_surrogate(c)) {
2020-09-06 18:53:08 +02:00
if (pos < len && !cesu8) {
c1 = src[pos];
if (is_lo_surrogate(c1)) {
2020-09-06 18:53:08 +02:00
pos++;
c = from_surrogate(c, c1);
2020-09-06 18:53:08 +02:00
} 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 */
2023-12-22 11:03:44 +01:00
p = container_of(ptr, JSString, u);
2020-09-06 18:53:08 +02:00
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
}
static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len)
{
int c, i;
for(i = 0; i < len; i++) {
c = src1[i] - src2[i];
if (c != 0)
return c;
}
return 0;
}
static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len)
{
int c, i;
for(i = 0; i < len; i++) {
c = src1[i] - src2[i];
if (c != 0)
return c;
}
return 0;
}
static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2,
int pos2, int len)
2020-09-06 18:53:08 +02:00
{
int res;
if (likely(!p1->is_wide_char)) {
if (likely(!p2->is_wide_char))
res = memcmp(p1->u.str8 + pos1, p2->u.str8 + pos2, len);
2020-09-06 18:53:08 +02:00
else
res = -memcmp16_8(p2->u.str16 + pos2, p1->u.str8 + pos1, len);
2020-09-06 18:53:08 +02:00
} else {
if (!p2->is_wide_char)
res = memcmp16_8(p1->u.str16 + pos1, p2->u.str8 + pos2, len);
2020-09-06 18:53:08 +02:00
else
res = memcmp16(p1->u.str16 + pos1, p2->u.str16 + pos2, len);
2020-09-06 18:53:08 +02:00
}
return res;
}
/* return < 0, 0 or > 0 */
static int js_string_compare(JSContext *ctx,
const JSString *p1, const JSString *p2)
{
int res, len;
len = min_int(p1->len, p2->len);
res = js_string_memcmp(p1, 0, p2, 0, len);
2020-09-06 18:53:08 +02:00
if (res == 0) {
if (p1->len == p2->len)
res = 0;
else if (p1->len < p2->len)
res = -1;
else
res = 1;
}
return res;
}
static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len)
{
if (p->is_wide_char) {
memcpy(dst, p->u.str16 + offset, len * 2);
} else {
const uint8_t *src1 = p->u.str8 + offset;
int i;
for(i = 0; i < len; i++)
dst[i] = src1[i];
}
}
static JSValue JS_ConcatString1(JSContext *ctx,
const JSString *p1, const JSString *p2)
{
JSString *p;
uint32_t len;
int is_wide_char;
len = p1->len + p2->len;
if (len > JS_STRING_LEN_MAX)
return JS_ThrowInternalError(ctx, "string too long");
is_wide_char = p1->is_wide_char | p2->is_wide_char;
p = js_alloc_string(ctx, len, is_wide_char);
if (!p)
return JS_EXCEPTION;
if (!is_wide_char) {
memcpy(p->u.str8, p1->u.str8, p1->len);
memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len);
p->u.str8[len] = '\0';
} else {
copy_str16(p->u.str16, p1, 0, p1->len);
copy_str16(p->u.str16 + p1->len, p2, 0, p2->len);
}
return JS_MKPTR(JS_TAG_STRING, p);
}
static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op2) {
if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) {
JSString *p2 = JS_VALUE_GET_STRING(op2);
size_t size1;
if (p2->len == 0)
return TRUE;
if (p1->header.ref_count != 1)
return FALSE;
size1 = js_malloc_usable_size(ctx, p1);
if (p1->is_wide_char) {
if (size1 >= sizeof(*p1) + ((p1->len + p2->len) << 1)) {
if (p2->is_wide_char) {
memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1);
p1->len += p2->len;
return TRUE;
} else {
size_t i;
for (i = 0; i < p2->len; i++) {
p1->u.str16[p1->len++] = p2->u.str8[i];
}
return TRUE;
}
}
} else if (!p2->is_wide_char) {
if (size1 >= sizeof(*p1) + p1->len + p2->len + 1) {
memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len);
p1->len += p2->len;
p1->u.str8[p1->len] = '\0';
return TRUE;
}
}
}
return FALSE;
}
static JSValue JS_ConcatString2(JSContext *ctx, JSValue op1, JSValue op2)
{
JSValue ret;
JSString *p1, *p2;
p1 = JS_VALUE_GET_STRING(op1);
if (JS_ConcatStringInPlace(ctx, p1, op2)) {
JS_FreeValue(ctx, op2);
return op1;
}
p2 = JS_VALUE_GET_STRING(op2);
ret = JS_ConcatString1(ctx, p1, p2);
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
return ret;
}
/* Return the character at position 'idx'. 'val' must be a string or rope */
static int string_rope_get(JSValueConst val, uint32_t idx)
{
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) {
return string_get(JS_VALUE_GET_STRING(val), idx);
} else {
JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val);
uint32_t len;
if (JS_VALUE_GET_TAG(r->left) == JS_TAG_STRING)
len = JS_VALUE_GET_STRING(r->left)->len;
else
len = JS_VALUE_GET_STRING_ROPE(r->left)->len;
if (idx < len)
return string_rope_get(r->left, idx);
else
return string_rope_get(r->right, idx - len);
}
}
typedef struct {
JSValueConst stack[JS_STRING_ROPE_MAX_DEPTH];
int stack_len;
} JSStringRopeIter;
static void string_rope_iter_init(JSStringRopeIter *s, JSValueConst val)
{
s->stack_len = 0;
s->stack[s->stack_len++] = val;
}
/* iterate thru a rope and return the strings in order */
static JSString *string_rope_iter_next(JSStringRopeIter *s)
{
JSValueConst val;
JSStringRope *r;
if (s->stack_len == 0)
return NULL;
val = s->stack[--s->stack_len];
for(;;) {
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING)
return JS_VALUE_GET_STRING(val);
r = JS_VALUE_GET_STRING_ROPE(val);
assert(s->stack_len < JS_STRING_ROPE_MAX_DEPTH);
s->stack[s->stack_len++] = r->right;
val = r->left;
}
}
static uint32_t string_rope_get_len(JSValueConst val)
{
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING)
return JS_VALUE_GET_STRING(val)->len;
else
return JS_VALUE_GET_STRING_ROPE(val)->len;
}
static int js_string_rope_compare(JSContext *ctx, JSValueConst op1,
JSValueConst op2, BOOL eq_only)
{
uint32_t len1, len2, len, pos1, pos2, l;
int res;
JSStringRopeIter it1, it2;
JSString *p1, *p2;
len1 = string_rope_get_len(op1);
len2 = string_rope_get_len(op2);
/* no need to go further for equality test if
different length */
if (eq_only && len1 != len2)
return 1;
len = min_uint32(len1, len2);
string_rope_iter_init(&it1, op1);
string_rope_iter_init(&it2, op2);
p1 = string_rope_iter_next(&it1);
p2 = string_rope_iter_next(&it2);
pos1 = 0;
pos2 = 0;
while (len != 0) {
l = min_uint32(p1->len - pos1, p2->len - pos2);
l = min_uint32(l, len);
res = js_string_memcmp(p1, pos1, p2, pos2, l);
if (res != 0)
return res;
len -= l;
pos1 += l;
if (pos1 >= p1->len) {
p1 = string_rope_iter_next(&it1);
pos1 = 0;
}
pos2 += l;
if (pos2 >= p2->len) {
p2 = string_rope_iter_next(&it2);
pos2 = 0;
}
}
if (len1 == len2)
res = 0;
else if (len1 < len2)
res = -1;
else
res = 1;
return res;
}
/* 'rope' must be a rope. return a string and modify the rope so that
it won't need to be linearized again. */
static JSValue js_linearize_string_rope(JSContext *ctx, JSValue rope)
{
StringBuffer b_s, *b = &b_s;
JSStringRope *r;
JSValue ret;
r = JS_VALUE_GET_STRING_ROPE(rope);
/* check whether it is already linearized */
if (JS_VALUE_GET_TAG(r->right) == JS_TAG_STRING &&
JS_VALUE_GET_STRING(r->right)->len == 0) {
ret = JS_DupValue(ctx, r->left);
JS_FreeValue(ctx, rope);
return ret;
}
if (string_buffer_init2(ctx, b, r->len, r->is_wide_char))
goto fail;
if (string_buffer_concat_value(b, rope))
goto fail;
ret = string_buffer_end(b);
if (r->header.ref_count > 1) {
/* update the rope so that it won't need to be linearized again */
JS_FreeValue(ctx, r->left);
JS_FreeValue(ctx, r->right);
r->left = JS_DupValue(ctx, ret);
r->right = JS_AtomToString(ctx, JS_ATOM_empty_string);
}
JS_FreeValue(ctx, rope);
return ret;
fail:
JS_FreeValue(ctx, rope);
return JS_EXCEPTION;
}
static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope);
/* op1 and op2 must be strings or string ropes */
static JSValue js_new_string_rope(JSContext *ctx, JSValue op1, JSValue op2)
{
uint32_t len;
int is_wide_char, depth;
JSStringRope *r;
JSValue res;
if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) {
JSString *p1 = JS_VALUE_GET_STRING(op1);
len = p1->len;
is_wide_char = p1->is_wide_char;
depth = 0;
} else {
JSStringRope *r1 = JS_VALUE_GET_STRING_ROPE(op1);
len = r1->len;
is_wide_char = r1->is_wide_char;
depth = r1->depth;
}
if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) {
JSString *p2 = JS_VALUE_GET_STRING(op2);
len += p2->len;
is_wide_char |= p2->is_wide_char;
} else {
JSStringRope *r2 = JS_VALUE_GET_STRING_ROPE(op2);
len += r2->len;
is_wide_char |= r2->is_wide_char;
depth = max_int(depth, r2->depth);
}
if (len > JS_STRING_LEN_MAX) {
JS_ThrowInternalError(ctx, "string too long");
goto fail;
}
r = js_malloc(ctx, sizeof(*r));
if (!r)
goto fail;
r->header.ref_count = 1;
r->len = len;
r->is_wide_char = is_wide_char;
r->depth = depth + 1;
r->left = op1;
r->right = op2;
res = JS_MKPTR(JS_TAG_STRING_ROPE, r);
if (r->depth > JS_STRING_ROPE_MAX_DEPTH) {
JSValue res2;
#ifdef DUMP_ROPE_REBALANCE
printf("rebalance: initial depth=%d\n", r->depth);
#endif
res2 = js_rebalancee_string_rope(ctx, res);
#ifdef DUMP_ROPE_REBALANCE
if (JS_VALUE_GET_TAG(res2) == JS_TAG_STRING_ROPE)
printf("rebalance: final depth=%d\n", JS_VALUE_GET_STRING_ROPE(res2)->depth);
#endif
JS_FreeValue(ctx, res);
return res2;
} else {
return res;
}
fail:
JS_FreeValue(ctx, op1);
JS_FreeValue(ctx, op2);
return JS_EXCEPTION;
}
#define ROPE_N_BUCKETS 44
/* Fibonacii numbers starting from F_2 */
static const uint32_t rope_bucket_len[ROPE_N_BUCKETS] = {
1, 2, 3, 5,
8, 13, 21, 34,
55, 89, 144, 233,
377, 610, 987, 1597,
2584, 4181, 6765, 10946,
17711, 28657, 46368, 75025,
121393, 196418, 317811, 514229,
832040, 1346269, 2178309, 3524578,
5702887, 9227465, 14930352, 24157817,
39088169, 63245986, 102334155, 165580141,
267914296, 433494437, 701408733, 1134903170, /* > JS_STRING_LEN_MAX */
};
static int js_rebalancee_string_rope_rec(JSContext *ctx, JSValue *buckets,
JSValueConst val)
{
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) {
JSString *p = JS_VALUE_GET_STRING(val);
uint32_t len, i;
JSValue a, b;
len = p->len;
if (len == 0)
return 0; /* nothing to do */
/* find the bucket i so that rope_bucket_len[i] <= len <
rope_bucket_len[i + 1] and concatenate the ropes in the
buckets before */
a = JS_NULL;
i = 0;
while (len >= rope_bucket_len[i + 1]) {
b = buckets[i];
if (!JS_IsNull(b)) {
buckets[i] = JS_NULL;
if (JS_IsNull(a)) {
a = b;
} else {
a = js_new_string_rope(ctx, b, a);
if (JS_IsException(a))
return -1;
}
}
i++;
}
if (!JS_IsNull(a)) {
a = js_new_string_rope(ctx, a, JS_DupValue(ctx, val));
if (JS_IsException(a))
return -1;
} else {
a = JS_DupValue(ctx, val);
}
while (!JS_IsNull(buckets[i])) {
a = js_new_string_rope(ctx, buckets[i], a);
buckets[i] = JS_NULL;
if (JS_IsException(a))
return -1;
i++;
}
buckets[i] = a;
} else {
JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val);
js_rebalancee_string_rope_rec(ctx, buckets, r->left);
js_rebalancee_string_rope_rec(ctx, buckets, r->right);
}
return 0;
}
/* Return a new rope which is balanced. Algorithm from "Ropes: an
Alternative to Strings", Hans-J. Boehm, Russ Atkinson and Michael
Plass. */
static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope)
{
JSValue buckets[ROPE_N_BUCKETS], a, b;
int i;
for(i = 0; i < ROPE_N_BUCKETS; i++)
buckets[i] = JS_NULL;
if (js_rebalancee_string_rope_rec(ctx, buckets, rope))
goto fail;
a = JS_NULL;
for(i = 0; i < ROPE_N_BUCKETS; i++) {
b = buckets[i];
if (!JS_IsNull(b)) {
buckets[i] = JS_NULL;
if (JS_IsNull(a)) {
a = b;
} else {
a = js_new_string_rope(ctx, b, a);
if (JS_IsException(a))
goto fail;
}
}
}
/* fail safe */
if (JS_IsNull(a))
return JS_AtomToString(ctx, JS_ATOM_empty_string);
else
return a;
fail:
for(i = 0; i < ROPE_N_BUCKETS; i++) {
JS_FreeValue(ctx, buckets[i]);
}
return JS_EXCEPTION;
}
/* op1 and op2 are converted to strings. For convenience, op1 or op2 =
2020-09-06 18:53:08 +02:00
JS_EXCEPTION are accepted and return JS_EXCEPTION. */
static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2)
{
JSString *p1, *p2;
if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING &&
JS_VALUE_GET_TAG(op1) != JS_TAG_STRING_ROPE)) {
2020-09-06 18:53:08 +02:00
op1 = JS_ToStringFree(ctx, op1);
if (JS_IsException(op1)) {
JS_FreeValue(ctx, op2);
return JS_EXCEPTION;
}
}
if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING &&
JS_VALUE_GET_TAG(op2) != JS_TAG_STRING_ROPE)) {
2020-09-06 18:53:08 +02:00
op2 = JS_ToStringFree(ctx, op2);
if (JS_IsException(op2)) {
JS_FreeValue(ctx, op1);
return JS_EXCEPTION;
}
}
/* normal concatenation for short strings */
if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) {
p2 = JS_VALUE_GET_STRING(op2);
if (p2->len == 0) {
JS_FreeValue(ctx, op2);
return op1;
}
if (p2->len <= JS_STRING_ROPE_SHORT_LEN) {
if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) {
p1 = JS_VALUE_GET_STRING(op1);
if (p1->len <= JS_STRING_ROPE_SHORT2_LEN) {
return JS_ConcatString2(ctx, op1, op2);
} else {
return js_new_string_rope(ctx, op1, op2);
}
} else {
JSStringRope *r1;
r1 = JS_VALUE_GET_STRING_ROPE(op1);
if (JS_VALUE_GET_TAG(r1->right) == JS_TAG_STRING &&
JS_VALUE_GET_STRING(r1->right)->len <= JS_STRING_ROPE_SHORT_LEN) {
JSValue val, ret;
val = JS_ConcatString2(ctx, JS_DupValue(ctx, r1->right), op2);
if (JS_IsException(val)) {
JS_FreeValue(ctx, op1);
return JS_EXCEPTION;
}
ret = js_new_string_rope(ctx, JS_DupValue(ctx, r1->left), val);
JS_FreeValue(ctx, op1);
return ret;
}
}
}
} else if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) {
JSStringRope *r2;
p1 = JS_VALUE_GET_STRING(op1);
if (p1->len == 0) {
JS_FreeValue(ctx, op1);
return op2;
}
r2 = JS_VALUE_GET_STRING_ROPE(op2);
if (JS_VALUE_GET_TAG(r2->left) == JS_TAG_STRING &&
JS_VALUE_GET_STRING(r2->left)->len <= JS_STRING_ROPE_SHORT_LEN) {
JSValue val, ret;
val = JS_ConcatString2(ctx, op1, JS_DupValue(ctx, r2->left));
if (JS_IsException(val)) {
JS_FreeValue(ctx, op2);
return JS_EXCEPTION;
}
ret = js_new_string_rope(ctx, val, JS_DupValue(ctx, r2->right));
JS_FreeValue(ctx, op2);
return ret;
}
2020-09-06 18:53:08 +02:00
}
return js_new_string_rope(ctx, op1, op2);
2020-09-06 18:53:08 +02:00
}
/* 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);
}
2020-09-06 19:10:15 +02:00
static inline uint32_t *prop_hash_end(JSShape *sh)
{
return (uint32_t *)sh;
}
2020-09-06 18:53:08 +02:00
static inline void *get_alloc_from_shape(JSShape *sh)
{
2020-09-06 19:10:15 +02:00
return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1);
2020-09-06 18:53:08 +02:00
}
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;
2020-09-06 19:10:15 +02:00
memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) *
2020-09-06 18:53:08 +02:00
hash_size);
sh->prop_hash_mask = hash_size - 1;
sh->prop_size = prop_size;
2020-09-06 19:07:30 +02:00
sh->prop_count = 0;
sh->deleted_prop_count = 0;
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
/* insert in the hash table */
sh->hash = shape_initial_hash(proto);
sh->is_hashed = TRUE;
sh->has_small_array_index = FALSE;
js_shape_hash_link(ctx->rt, sh);
return sh;
}
static JSShape *js_new_shape(JSContext *ctx, JSObject *proto)
{
return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE,
JS_PROP_INITIAL_SIZE);
}
/* The shape is cloned. The new shape is not inserted in the shape
hash table */
static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1)
{
JSShape *sh;
void *sh_alloc, *sh_alloc1;
size_t size;
JSShapeProperty *pr;
uint32_t i, hash_size;
hash_size = sh1->prop_hash_mask + 1;
size = get_shape_size(hash_size, sh1->prop_size);
sh_alloc = js_malloc(ctx, size);
if (!sh_alloc)
return NULL;
sh_alloc1 = get_alloc_from_shape(sh1);
memcpy(sh_alloc, sh_alloc1, size);
sh = get_shape_from_alloc(sh_alloc, hash_size);
sh->header.ref_count = 1;
add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE);
sh->is_hashed = FALSE;
if (sh->proto) {
JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
}
for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) {
JS_DupAtom(ctx, pr->atom);
}
return sh;
}
static JSShape *js_dup_shape(JSShape *sh)
{
sh->header.ref_count++;
return sh;
}
static void js_free_shape0(JSRuntime *rt, JSShape *sh)
{
uint32_t i;
JSShapeProperty *pr;
assert(sh->header.ref_count == 0);
if (sh->is_hashed)
js_shape_hash_unlink(rt, sh);
if (sh->proto != NULL) {
JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
}
pr = get_shape_prop(sh);
for(i = 0; i < sh->prop_count; i++) {
JS_FreeAtomRT(rt, pr->atom);
pr++;
}
remove_gc_object(&sh->header);
js_free_rt(rt, get_alloc_from_shape(sh));
}
static void js_free_shape(JSRuntime *rt, JSShape *sh)
{
if (unlikely(--sh->header.ref_count <= 0)) {
js_free_shape0(rt, sh);
}
}
static void js_free_shape_null(JSRuntime *rt, JSShape *sh)
{
if (sh)
js_free_shape(rt, sh);
}
/* make space to hold at least 'count' properties */
static no_inline int resize_properties(JSContext *ctx, JSShape **psh,
JSObject *p, uint32_t count)
{
JSShape *sh;
uint32_t new_size, new_hash_size, new_hash_mask, i;
JSShapeProperty *pr;
void *sh_alloc;
intptr_t h;
JSShape *old_sh;
2020-09-06 18:53:08 +02:00
sh = *psh;
new_size = max_int(count, sh->prop_size * 3 / 2);
/* Reallocate prop array first to avoid crash or size inconsistency
in case of memory allocation failure */
if (p) {
JSProperty *new_prop;
new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size);
if (unlikely(!new_prop))
return -1;
p->prop = new_prop;
}
new_hash_size = sh->prop_hash_mask + 1;
while (new_hash_size < new_size)
new_hash_size = 2 * new_hash_size;
/* resize the property shapes. Using js_realloc() is not possible in
case the GC runs during the allocation */
old_sh = sh;
sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size));
if (!sh_alloc)
return -1;
sh = get_shape_from_alloc(sh_alloc, new_hash_size);
list_del(&old_sh->header.link);
/* copy all the shape properties */
memcpy(sh, old_sh,
sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count);
list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
if (new_hash_size != (sh->prop_hash_mask + 1)) {
/* resize the hash table and the properties */
new_hash_mask = new_hash_size - 1;
sh->prop_hash_mask = new_hash_mask;
2020-09-06 19:10:15 +02:00
memset(prop_hash_end(sh) - new_hash_size, 0,
sizeof(prop_hash_end(sh)[0]) * new_hash_size);
2020-09-06 18:53:08 +02:00
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);
2020-09-06 19:10:15 +02:00
pr->hash_next = prop_hash_end(sh)[-h - 1];
prop_hash_end(sh)[-h - 1] = i + 1;
2020-09-06 18:53:08 +02:00
}
}
} else {
/* just copy the previous hash table */
memcpy(prop_hash_end(sh) - new_hash_size, prop_hash_end(old_sh) - new_hash_size,
sizeof(prop_hash_end(sh)[0]) * new_hash_size);
2020-09-06 18:53:08 +02:00
}
js_free(ctx, get_alloc_from_shape(old_sh));
2020-09-06 18:53:08 +02:00
*psh = sh;
sh->prop_size = new_size;
return 0;
}
2020-09-06 19:07:30 +02:00
/* remove the deleted properties. */
static int compact_properties(JSContext *ctx, JSObject *p)
{
JSShape *sh, *old_sh;
void *sh_alloc;
intptr_t h;
uint32_t new_hash_size, i, j, new_hash_mask, new_size;
JSShapeProperty *old_pr, *pr;
JSProperty *prop, *new_prop;
2024-02-10 16:18:11 +01:00
2020-09-06 19:07:30 +02:00
sh = p->shape;
assert(!sh->is_hashed);
new_size = max_int(JS_PROP_INITIAL_SIZE,
sh->prop_count - sh->deleted_prop_count);
assert(new_size <= sh->prop_size);
new_hash_size = sh->prop_hash_mask + 1;
while ((new_hash_size / 2) >= new_size)
new_hash_size = new_hash_size / 2;
new_hash_mask = new_hash_size - 1;
/* resize the hash table and the properties */
old_sh = sh;
sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size));
if (!sh_alloc)
return -1;
sh = get_shape_from_alloc(sh_alloc, new_hash_size);
list_del(&old_sh->header.link);
memcpy(sh, old_sh, sizeof(JSShape));
list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list);
2024-02-10 16:18:11 +01:00
2020-09-06 19:10:15 +02:00
memset(prop_hash_end(sh) - new_hash_size, 0,
sizeof(prop_hash_end(sh)[0]) * new_hash_size);
2020-09-06 19:07:30 +02:00
j = 0;
old_pr = old_sh->prop;
pr = sh->prop;
prop = p->prop;
for(i = 0; i < sh->prop_count; i++) {
if (old_pr->atom != JS_ATOM_NULL) {
pr->atom = old_pr->atom;
pr->flags = old_pr->flags;
h = ((uintptr_t)old_pr->atom & new_hash_mask);
2020-09-06 19:10:15 +02:00
pr->hash_next = prop_hash_end(sh)[-h - 1];
prop_hash_end(sh)[-h - 1] = j + 1;
2020-09-06 19:07:30 +02:00
prop[j] = prop[i];
j++;
pr++;
}
old_pr++;
}
assert(j == (sh->prop_count - sh->deleted_prop_count));
sh->prop_hash_mask = new_hash_mask;
sh->prop_size = new_size;
sh->deleted_prop_count = 0;
sh->prop_count = j;
p->shape = sh;
js_free(ctx, get_alloc_from_shape(old_sh));
2024-02-10 16:18:11 +01:00
2020-09-06 19:07:30 +02:00
/* reduce the size of the object properties */
new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size);
if (new_prop)
p->prop = new_prop;
return 0;
}
2020-09-06 18:53:08 +02:00
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;
2020-09-06 19:10:15 +02:00
pr->hash_next = prop_hash_end(sh)[-h - 1];
prop_hash_end(sh)[-h - 1] = sh->prop_count;
2020-09-06 18:53:08 +02:00
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;
2024-02-10 16:18:11 +01:00
2020-09-06 18:53:08 +02:00
printf("JSShapes: {\n");
printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS");
for(i = 0; i < rt->shape_hash_size; i++) {
for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) {
JS_DumpShape(rt, i, sh);
assert(sh->is_hashed);
}
}
/* dump non-hashed shapes */
list_for_each(el, &rt->gc_obj_list) {
gp = list_entry(el, JSGCObjectHeader, link);
if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) {
p = (JSObject *)gp;
if (!p->shape->is_hashed) {
JS_DumpShape(rt, -1, p->shape);
}
}
}
printf("}\n");
}
static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id)
{
JSObject *p;
js_trigger_gc(ctx->rt, sizeof(JSObject));
p = js_malloc(ctx, sizeof(JSObject));
if (unlikely(!p))
goto fail;
p->class_id = class_id;
p->extensible = TRUE;
p->free_mark = 0;
p->is_exotic = 0;
p->fast_array = 0;
p->is_constructor = 0;
p->has_immutable_prototype = 0;
2020-09-06 18:53:08 +02:00
p->tmp_mark = 0;
2020-09-06 19:10:15 +02:00
p->is_HTMLDDA = 0;
p->weakref_count = 0;
2020-09-06 18:53:08 +02:00
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:
2020-09-06 19:10:15 +02:00
case JS_CLASS_UINT8C_ARRAY:
case JS_CLASS_INT8_ARRAY:
case JS_CLASS_UINT8_ARRAY:
case JS_CLASS_INT16_ARRAY:
case JS_CLASS_UINT16_ARRAY:
case JS_CLASS_INT32_ARRAY:
case JS_CLASS_UINT32_ARRAY:
case JS_CLASS_BIG_INT64_ARRAY:
case JS_CLASS_BIG_UINT64_ARRAY:
case JS_CLASS_FLOAT16_ARRAY:
2020-09-06 19:10:15 +02:00
case JS_CLASS_FLOAT32_ARRAY:
case JS_CLASS_FLOAT64_ARRAY:
2020-09-06 18:53:08 +02:00
p->is_exotic = 1;
p->fast_array = 1;
p->u.array.u.ptr = NULL;
p->u.array.count