54062 lines
1.7 MiB
54062 lines
1.7 MiB
/*
|
|
* QuickJS Javascript Engine
|
|
*
|
|
* Copyright (c) 2017-2021 Fabrice Bellard
|
|
* Copyright (c) 2017-2021 Charlie Gordon
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include <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__)
|
|
#include <malloc.h>
|
|
#elif defined(__FreeBSD__)
|
|
#include <malloc_np.h>
|
|
#endif
|
|
|
|
#include "cutils.h"
|
|
#include "list.h"
|
|
#include "quickjs.h"
|
|
#include "libregexp.h"
|
|
#ifdef CONFIG_BIGNUM
|
|
#include "libbf.h"
|
|
#endif
|
|
|
|
#define OPTIMIZE 1
|
|
#define SHORT_OPCODES 1
|
|
#if defined(EMSCRIPTEN)
|
|
#define DIRECT_DISPATCH 0
|
|
#else
|
|
#define DIRECT_DISPATCH 1
|
|
#endif
|
|
|
|
#if defined(__APPLE__)
|
|
#define MALLOC_OVERHEAD 0
|
|
#else
|
|
#define MALLOC_OVERHEAD 8
|
|
#endif
|
|
|
|
#if !defined(_WIN32)
|
|
/* define it if printf uses the RNDN rounding mode instead of RNDNA */
|
|
#define CONFIG_PRINTF_RNDN
|
|
#endif
|
|
|
|
/* define to include Atomics.* operations which depend on the OS
|
|
threads */
|
|
#if !defined(EMSCRIPTEN)
|
|
#define CONFIG_ATOMICS
|
|
#endif
|
|
|
|
#if !defined(EMSCRIPTEN)
|
|
/* enable stack limitation */
|
|
#define CONFIG_STACK_CHECK
|
|
#endif
|
|
|
|
|
|
/* dump object free */
|
|
//#define DUMP_FREE
|
|
//#define DUMP_CLOSURE
|
|
/* dump the bytecode of the compiled functions: combination of bits
|
|
1: dump pass 3 final byte code
|
|
2: dump pass 2 code
|
|
4: dump pass 1 code
|
|
8: dump stdlib functions
|
|
16: dump bytecode in hex
|
|
32: dump line number table
|
|
*/
|
|
//#define DUMP_BYTECODE (1)
|
|
/* dump the occurence of the automatic GC */
|
|
//#define DUMP_GC
|
|
/* dump objects freed by the garbage collector */
|
|
//#define DUMP_GC_FREE
|
|
/* dump objects leaking when freeing the runtime */
|
|
//#define DUMP_LEAKS 1
|
|
/* dump memory usage before running the garbage collector */
|
|
//#define DUMP_MEM
|
|
//#define DUMP_OBJECTS /* dump objects in JS_FreeContext */
|
|
//#define DUMP_ATOMS /* dump atoms in JS_FreeContext */
|
|
//#define DUMP_SHAPES /* dump shapes in JS_FreeContext */
|
|
//#define DUMP_MODULE_RESOLVE
|
|
//#define DUMP_PROMISE
|
|
//#define DUMP_READ_OBJECT
|
|
|
|
/* test the GC by forcing it before each object allocation */
|
|
//#define FORCE_GC_AT_MALLOC
|
|
|
|
#ifdef CONFIG_ATOMICS
|
|
#include <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) */
|
|
#ifdef CONFIG_BIGNUM
|
|
JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */
|
|
#endif
|
|
JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */
|
|
JS_CLASS_DATAVIEW, /* u.typed_array */
|
|
#ifdef CONFIG_BIGNUM
|
|
JS_CLASS_BIG_INT, /* u.object_data */
|
|
JS_CLASS_BIG_FLOAT, /* u.object_data */
|
|
JS_CLASS_FLOAT_ENV, /* u.float_env */
|
|
JS_CLASS_BIG_DECIMAL, /* u.object_data */
|
|
JS_CLASS_OPERATOR_SET, /* u.operator_set */
|
|
#endif
|
|
JS_CLASS_MAP, /* u.map_state */
|
|
JS_CLASS_SET, /* u.map_state */
|
|
JS_CLASS_WEAKMAP, /* u.map_state */
|
|
JS_CLASS_WEAKSET, /* u.map_state */
|
|
JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */
|
|
JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */
|
|
JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */
|
|
JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */
|
|
JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */
|
|
JS_CLASS_GENERATOR, /* u.generator_data */
|
|
JS_CLASS_PROXY, /* u.proxy_data */
|
|
JS_CLASS_PROMISE, /* u.promise_data */
|
|
JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */
|
|
JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */
|
|
JS_CLASS_ASYNC_FUNCTION, /* u.func */
|
|
JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */
|
|
JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */
|
|
JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */
|
|
JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */
|
|
JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */
|
|
|
|
JS_CLASS_INIT_COUNT, /* last entry for predefined classes */
|
|
};
|
|
|
|
/* number of typed array types */
|
|
#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1)
|
|
static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT];
|
|
#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY])
|
|
|
|
typedef enum JSErrorEnum {
|
|
JS_EVAL_ERROR,
|
|
JS_RANGE_ERROR,
|
|
JS_REFERENCE_ERROR,
|
|
JS_SYNTAX_ERROR,
|
|
JS_TYPE_ERROR,
|
|
JS_URI_ERROR,
|
|
JS_INTERNAL_ERROR,
|
|
JS_AGGREGATE_ERROR,
|
|
|
|
JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */
|
|
} JSErrorEnum;
|
|
|
|
#define JS_MAX_LOCAL_VARS 65536
|
|
#define JS_STACK_SIZE_MAX 65534
|
|
#define JS_STRING_LEN_MAX ((1 << 30) - 1)
|
|
|
|
#define __exception __attribute__((warn_unused_result))
|
|
|
|
typedef struct JSShape JSShape;
|
|
typedef struct JSString JSString;
|
|
typedef struct JSString JSAtomStruct;
|
|
|
|
typedef enum {
|
|
JS_GC_PHASE_NONE,
|
|
JS_GC_PHASE_DECREF,
|
|
JS_GC_PHASE_REMOVE_CYCLES,
|
|
} JSGCPhaseEnum;
|
|
|
|
typedef enum OPCodeEnum OPCodeEnum;
|
|
|
|
#ifdef CONFIG_BIGNUM
|
|
/* function pointers are used for numeric operations so that it is
|
|
possible to remove some numeric types */
|
|
typedef struct {
|
|
JSValue (*to_string)(JSContext *ctx, JSValueConst val);
|
|
JSValue (*from_string)(JSContext *ctx, const char *buf,
|
|
int radix, int flags, slimb_t *pexponent);
|
|
int (*unary_arith)(JSContext *ctx,
|
|
JSValue *pres, OPCodeEnum op, JSValue op1);
|
|
int (*binary_arith)(JSContext *ctx, OPCodeEnum op,
|
|
JSValue *pres, JSValue op1, JSValue op2);
|
|
int (*compare)(JSContext *ctx, OPCodeEnum op,
|
|
JSValue op1, JSValue op2);
|
|
/* only for bigfloat: */
|
|
JSValue (*mul_pow10_to_float64)(JSContext *ctx, const bf_t *a,
|
|
int64_t exponent);
|
|
int (*mul_pow10)(JSContext *ctx, JSValue *sp);
|
|
} JSNumericOperations;
|
|
#endif
|
|
|
|
struct JSRuntime {
|
|
JSMallocFunctions mf;
|
|
JSMallocState malloc_state;
|
|
const char *rt_info;
|
|
|
|
int atom_hash_size; /* power of two */
|
|
int atom_count;
|
|
int atom_size;
|
|
int atom_count_resize; /* resize hash table at this count */
|
|
uint32_t *atom_hash;
|
|
JSAtomStruct **atom_array;
|
|
int atom_free_index; /* 0 = none */
|
|
|
|
int class_count; /* size of class_array */
|
|
JSClass *class_array;
|
|
|
|
struct list_head context_list; /* list of JSContext.link */
|
|
/* list of JSGCObjectHeader.link. List of allocated GC objects (used
|
|
by the garbage collector) */
|
|
struct list_head gc_obj_list;
|
|
/* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */
|
|
struct list_head gc_zero_ref_count_list;
|
|
struct list_head tmp_obj_list; /* used during GC */
|
|
JSGCPhaseEnum gc_phase : 8;
|
|
size_t malloc_gc_threshold;
|
|
#ifdef DUMP_LEAKS
|
|
struct list_head string_list; /* list of JSString.link */
|
|
#endif
|
|
/* stack limitation */
|
|
uintptr_t stack_size; /* in bytes, 0 if no limit */
|
|
uintptr_t stack_top;
|
|
uintptr_t stack_limit; /* lower stack limit */
|
|
|
|
JSValue current_exception;
|
|
/* true if inside an out of memory error, to avoid recursing */
|
|
BOOL in_out_of_memory : 8;
|
|
|
|
struct JSStackFrame *current_stack_frame;
|
|
|
|
JSInterruptHandler *interrupt_handler;
|
|
void *interrupt_opaque;
|
|
|
|
JSHostPromiseRejectionTracker *host_promise_rejection_tracker;
|
|
void *host_promise_rejection_tracker_opaque;
|
|
|
|
struct list_head job_list; /* list of JSJobEntry.link */
|
|
|
|
JSModuleNormalizeFunc *module_normalize_func;
|
|
JSModuleLoaderFunc *module_loader_func;
|
|
void *module_loader_opaque;
|
|
|
|
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
|
|
/* used to allocate, free and clone SharedArrayBuffers */
|
|
JSSharedArrayBufferFunctions sab_funcs;
|
|
|
|
/* Shape hash table */
|
|
int shape_hash_bits;
|
|
int shape_hash_size;
|
|
int shape_hash_count; /* number of hashed shapes */
|
|
JSShape **shape_hash;
|
|
#ifdef CONFIG_BIGNUM
|
|
bf_context_t bf_ctx;
|
|
JSNumericOperations bigint_ops;
|
|
JSNumericOperations bigfloat_ops;
|
|
JSNumericOperations bigdecimal_ops;
|
|
uint32_t operator_count;
|
|
#endif
|
|
void *user_opaque;
|
|
};
|
|
|
|
struct JSClass {
|
|
uint32_t class_id; /* 0 means free entry */
|
|
JSAtom class_name;
|
|
JSClassFinalizer *finalizer;
|
|
JSClassGCMark *gc_mark;
|
|
JSClassCall *call;
|
|
/* pointers for exotic behavior, can be NULL if none are present */
|
|
const JSClassExoticMethods *exotic;
|
|
};
|
|
|
|
#define JS_MODE_STRICT (1 << 0)
|
|
#define JS_MODE_STRIP (1 << 1)
|
|
#define JS_MODE_MATH (1 << 2)
|
|
|
|
typedef struct JSStackFrame {
|
|
struct JSStackFrame *prev_frame; /* NULL if first stack frame */
|
|
JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */
|
|
JSValue *arg_buf; /* arguments */
|
|
JSValue *var_buf; /* variables */
|
|
struct list_head var_ref_list; /* list of JSVarRef.link */
|
|
const uint8_t *cur_pc; /* only used in bytecode functions : PC of the
|
|
instruction after the call */
|
|
int arg_count;
|
|
int js_mode; /* 0 or JS_MODE_MATH for C functions */
|
|
/* only used in generators. Current stack pointer value. NULL if
|
|
the function is running. */
|
|
JSValue *cur_sp;
|
|
} JSStackFrame;
|
|
|
|
typedef enum {
|
|
JS_GC_OBJ_TYPE_JS_OBJECT,
|
|
JS_GC_OBJ_TYPE_FUNCTION_BYTECODE,
|
|
JS_GC_OBJ_TYPE_SHAPE,
|
|
JS_GC_OBJ_TYPE_VAR_REF,
|
|
JS_GC_OBJ_TYPE_ASYNC_FUNCTION,
|
|
JS_GC_OBJ_TYPE_JS_CONTEXT,
|
|
} JSGCObjectTypeEnum;
|
|
|
|
/* header for GC objects. GC objects are C data structures with a
|
|
reference count that can reference other GC objects. JS Objects are
|
|
a particular type of GC object. */
|
|
struct JSGCObjectHeader {
|
|
int ref_count; /* must come first, 32-bit */
|
|
JSGCObjectTypeEnum gc_obj_type : 4;
|
|
uint8_t mark : 4; /* used by the GC */
|
|
uint8_t dummy1; /* not used by the GC */
|
|
uint16_t dummy2; /* not used by the GC */
|
|
struct list_head link;
|
|
};
|
|
|
|
typedef struct JSVarRef {
|
|
union {
|
|
JSGCObjectHeader header; /* must come first */
|
|
struct {
|
|
int __gc_ref_count; /* corresponds to header.ref_count */
|
|
uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */
|
|
|
|
/* 0 : the JSVarRef is on the stack. header.link is an element
|
|
of JSStackFrame.var_ref_list.
|
|
1 : the JSVarRef is detached. header.link has the normal meanning
|
|
*/
|
|
uint8_t is_detached : 1;
|
|
uint8_t is_arg : 1;
|
|
uint16_t var_idx; /* index of the corresponding function variable on
|
|
the stack */
|
|
};
|
|
};
|
|
JSValue *pvalue; /* pointer to the value, either on the stack or
|
|
to 'value' */
|
|
JSValue value; /* used when the variable is no longer on the stack */
|
|
} JSVarRef;
|
|
|
|
#ifdef CONFIG_BIGNUM
|
|
typedef struct JSFloatEnv {
|
|
limb_t prec;
|
|
bf_flags_t flags;
|
|
unsigned int status;
|
|
} JSFloatEnv;
|
|
|
|
/* the same structure is used for big integers and big floats. Big
|
|
integers are never infinite or NaNs */
|
|
typedef struct JSBigFloat {
|
|
JSRefCountHeader header; /* must come first, 32-bit */
|
|
bf_t num;
|
|
} JSBigFloat;
|
|
|
|
typedef struct JSBigDecimal {
|
|
JSRefCountHeader header; /* must come first, 32-bit */
|
|
bfdec_t num;
|
|
} JSBigDecimal;
|
|
#endif
|
|
|
|
typedef enum {
|
|
JS_AUTOINIT_ID_PROTOTYPE,
|
|
JS_AUTOINIT_ID_MODULE_NS,
|
|
JS_AUTOINIT_ID_PROP,
|
|
} JSAutoInitIDEnum;
|
|
|
|
/* must be large enough to have a negligible runtime cost and small
|
|
enough to call the interrupt callback often. */
|
|
#define JS_INTERRUPT_COUNTER_INIT 10000
|
|
|
|
struct JSContext {
|
|
JSGCObjectHeader header; /* must come first */
|
|
JSRuntime *rt;
|
|
struct list_head link;
|
|
|
|
uint16_t binary_object_count;
|
|
int binary_object_size;
|
|
|
|
JSShape *array_shape; /* initial shape for Array objects */
|
|
|
|
JSValue *class_proto;
|
|
JSValue function_proto;
|
|
JSValue function_ctor;
|
|
JSValue array_ctor;
|
|
JSValue regexp_ctor;
|
|
JSValue promise_ctor;
|
|
JSValue native_error_proto[JS_NATIVE_ERROR_COUNT];
|
|
JSValue iterator_proto;
|
|
JSValue async_iterator_proto;
|
|
JSValue array_proto_values;
|
|
JSValue throw_type_error;
|
|
JSValue eval_obj;
|
|
|
|
JSValue global_obj; /* global object */
|
|
JSValue global_var_obj; /* contains the global let/const definitions */
|
|
|
|
uint64_t random_state;
|
|
#ifdef CONFIG_BIGNUM
|
|
bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */
|
|
JSFloatEnv fp_env; /* global FP environment */
|
|
BOOL bignum_ext : 8; /* enable math mode */
|
|
BOOL allow_operator_overloading : 8;
|
|
#endif
|
|
/* when the counter reaches zero, JSRutime.interrupt_handler is called */
|
|
int interrupt_counter;
|
|
BOOL is_error_property_enabled;
|
|
|
|
struct list_head loaded_modules; /* list of JSModuleDef.link */
|
|
|
|
/* if NULL, RegExp compilation is not supported */
|
|
JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern,
|
|
JSValueConst flags);
|
|
/* if NULL, eval is not supported */
|
|
JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj,
|
|
const char *input, size_t input_len,
|
|
const char *filename, int flags, int scope_idx);
|
|
void *user_opaque;
|
|
};
|
|
|
|
typedef union JSFloat64Union {
|
|
double d;
|
|
uint64_t u64;
|
|
uint32_t u32[2];
|
|
} JSFloat64Union;
|
|
|
|
enum {
|
|
JS_ATOM_TYPE_STRING = 1,
|
|
JS_ATOM_TYPE_GLOBAL_SYMBOL,
|
|
JS_ATOM_TYPE_SYMBOL,
|
|
JS_ATOM_TYPE_PRIVATE,
|
|
};
|
|
|
|
enum {
|
|
JS_ATOM_HASH_SYMBOL,
|
|
JS_ATOM_HASH_PRIVATE,
|
|
};
|
|
|
|
typedef enum {
|
|
JS_ATOM_KIND_STRING,
|
|
JS_ATOM_KIND_SYMBOL,
|
|
JS_ATOM_KIND_PRIVATE,
|
|
} JSAtomKindEnum;
|
|
|
|
#define JS_ATOM_HASH_MASK ((1 << 30) - 1)
|
|
|
|
struct JSString {
|
|
JSRefCountHeader header; /* must come first, 32-bit */
|
|
uint32_t len : 31;
|
|
uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */
|
|
/* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3,
|
|
for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3
|
|
XXX: could change encoding to have one more bit in hash */
|
|
uint32_t hash : 30;
|
|
uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
|
|
uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */
|
|
#ifdef DUMP_LEAKS
|
|
struct list_head link; /* string list */
|
|
#endif
|
|
union {
|
|
uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */
|
|
uint16_t str16[0];
|
|
} u;
|
|
};
|
|
|
|
typedef struct JSClosureVar {
|
|
uint8_t is_local : 1;
|
|
uint8_t is_arg : 1;
|
|
uint8_t is_const : 1;
|
|
uint8_t is_lexical : 1;
|
|
uint8_t var_kind : 4; /* see JSVarKindEnum */
|
|
/* 8 bits available */
|
|
uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the
|
|
parent function. otherwise: index to a closure
|
|
variable of the parent function */
|
|
JSAtom var_name;
|
|
} JSClosureVar;
|
|
|
|
#define ARG_SCOPE_INDEX 1
|
|
#define ARG_SCOPE_END (-2)
|
|
|
|
typedef struct JSVarScope {
|
|
int parent; /* index into fd->scopes of the enclosing scope */
|
|
int first; /* index into fd->vars of the last variable in this scope */
|
|
} JSVarScope;
|
|
|
|
typedef enum {
|
|
/* XXX: add more variable kinds here instead of using bit fields */
|
|
JS_VAR_NORMAL,
|
|
JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */
|
|
JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator
|
|
function declaration */
|
|
JS_VAR_CATCH,
|
|
JS_VAR_FUNCTION_NAME, /* function expression name */
|
|
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;
|
|
|
|
/* XXX: could use a different structure in bytecode functions to save
|
|
memory */
|
|
typedef struct JSVarDef {
|
|
JSAtom var_name;
|
|
/* index into fd->scopes of this variable lexical scope */
|
|
int scope_level;
|
|
/* during compilation:
|
|
- if scope_level = 0: scope in which the variable is defined
|
|
- if scope_level != 0: index into fd->vars of the next
|
|
variable in the same or enclosing lexical scope
|
|
in a bytecode function:
|
|
index into fd->vars of the next
|
|
variable in the same or enclosing lexical scope
|
|
*/
|
|
int scope_next;
|
|
uint8_t is_const : 1;
|
|
uint8_t is_lexical : 1;
|
|
uint8_t is_captured : 1;
|
|
uint8_t var_kind : 4; /* see JSVarKindEnum */
|
|
/* only used during compilation: function pool index for lexical
|
|
variables with var_kind =
|
|
JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of
|
|
the definition of the 'var' variables (they have scope_level =
|
|
0) */
|
|
int func_pool_idx : 24; /* only used during compilation : index in
|
|
the constant pool for hoisted function
|
|
definition */
|
|
} JSVarDef;
|
|
|
|
/* for the encoding of the pc2line table */
|
|
#define PC2LINE_BASE (-1)
|
|
#define PC2LINE_RANGE 5
|
|
#define PC2LINE_OP_FIRST 1
|
|
#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE)
|
|
|
|
typedef enum JSFunctionKindEnum {
|
|
JS_FUNC_NORMAL = 0,
|
|
JS_FUNC_GENERATOR = (1 << 0),
|
|
JS_FUNC_ASYNC = (1 << 1),
|
|
JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC),
|
|
} JSFunctionKindEnum;
|
|
|
|
typedef struct JSFunctionBytecode {
|
|
JSGCObjectHeader header; /* must come first */
|
|
uint8_t js_mode;
|
|
uint8_t has_prototype : 1; /* true if a prototype field is necessary */
|
|
uint8_t has_simple_parameter_list : 1;
|
|
uint8_t is_derived_class_constructor : 1;
|
|
/* true if home_object needs to be initialized */
|
|
uint8_t need_home_object : 1;
|
|
uint8_t func_kind : 2;
|
|
uint8_t new_target_allowed : 1;
|
|
uint8_t super_call_allowed : 1;
|
|
uint8_t super_allowed : 1;
|
|
uint8_t arguments_allowed : 1;
|
|
uint8_t has_debug : 1;
|
|
uint8_t backtrace_barrier : 1; /* stop backtrace on this function */
|
|
uint8_t read_only_bytecode : 1;
|
|
/* XXX: 4 bits available */
|
|
uint8_t *byte_code_buf; /* (self pointer) */
|
|
int byte_code_len;
|
|
JSAtom func_name;
|
|
JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */
|
|
JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */
|
|
uint16_t arg_count;
|
|
uint16_t var_count;
|
|
uint16_t defined_arg_count; /* for length function property */
|
|
uint16_t stack_size; /* maximum stack size */
|
|
JSContext *realm; /* function realm */
|
|
JSValue *cpool; /* constant pool (self pointer) */
|
|
int cpool_count;
|
|
int closure_var_count;
|
|
struct {
|
|
/* debug info, move to separate structure to save memory? */
|
|
JSAtom filename;
|
|
int line_num;
|
|
int source_len;
|
|
int pc2line_len;
|
|
uint8_t *pc2line_buf;
|
|
char *source;
|
|
} debug;
|
|
} JSFunctionBytecode;
|
|
|
|
typedef struct JSBoundFunction {
|
|
JSValue func_obj;
|
|
JSValue this_val;
|
|
int argc;
|
|
JSValue argv[0];
|
|
} JSBoundFunction;
|
|
|
|
typedef enum JSIteratorKindEnum {
|
|
JS_ITERATOR_KIND_KEY,
|
|
JS_ITERATOR_KIND_VALUE,
|
|
JS_ITERATOR_KIND_KEY_AND_VALUE,
|
|
} JSIteratorKindEnum;
|
|
|
|
typedef struct JSForInIterator {
|
|
JSValue obj;
|
|
BOOL is_array;
|
|
uint32_t array_length;
|
|
uint32_t idx;
|
|
} JSForInIterator;
|
|
|
|
typedef struct JSRegExp {
|
|
JSString *pattern;
|
|
JSString *bytecode; /* also contains the flags */
|
|
} JSRegExp;
|
|
|
|
typedef struct JSProxyData {
|
|
JSValue target;
|
|
JSValue handler;
|
|
uint8_t is_func;
|
|
uint8_t is_revoked;
|
|
} JSProxyData;
|
|
|
|
typedef struct JSArrayBuffer {
|
|
int byte_length; /* 0 if detached */
|
|
uint8_t detached;
|
|
uint8_t shared; /* if shared, the array buffer cannot be detached */
|
|
uint8_t *data; /* NULL if detached */
|
|
struct list_head array_list;
|
|
void *opaque;
|
|
JSFreeArrayBufferDataFunc *free_func;
|
|
} JSArrayBuffer;
|
|
|
|
typedef struct JSTypedArray {
|
|
struct list_head link; /* link to arraybuffer */
|
|
JSObject *obj; /* back pointer to the TypedArray/DataView object */
|
|
JSObject *buffer; /* based array buffer */
|
|
uint32_t offset; /* offset in the array buffer */
|
|
uint32_t length; /* length in the array buffer */
|
|
} JSTypedArray;
|
|
|
|
typedef struct JSAsyncFunctionState {
|
|
JSValue this_val; /* 'this' generator argument */
|
|
int argc; /* number of function arguments */
|
|
BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */
|
|
JSStackFrame frame;
|
|
} JSAsyncFunctionState;
|
|
|
|
/* XXX: could use an object instead to avoid the
|
|
JS_TAG_ASYNC_FUNCTION tag for the GC */
|
|
typedef struct JSAsyncFunctionData {
|
|
JSGCObjectHeader header; /* must come first */
|
|
JSValue resolving_funcs[2];
|
|
BOOL is_active; /* true if the async function state is valid */
|
|
JSAsyncFunctionState func_state;
|
|
} JSAsyncFunctionData;
|
|
|
|
typedef enum {
|
|
/* binary operators */
|
|
JS_OVOP_ADD,
|
|
JS_OVOP_SUB,
|
|
JS_OVOP_MUL,
|
|
JS_OVOP_DIV,
|
|
JS_OVOP_MOD,
|
|
JS_OVOP_POW,
|
|
JS_OVOP_OR,
|
|
JS_OVOP_AND,
|
|
JS_OVOP_XOR,
|
|
JS_OVOP_SHL,
|
|
JS_OVOP_SAR,
|
|
JS_OVOP_SHR,
|
|
JS_OVOP_EQ,
|
|
JS_OVOP_LESS,
|
|
|
|
JS_OVOP_BINARY_COUNT,
|
|
/* unary operators */
|
|
JS_OVOP_POS = JS_OVOP_BINARY_COUNT,
|
|
JS_OVOP_NEG,
|
|
JS_OVOP_INC,
|
|
JS_OVOP_DEC,
|
|
JS_OVOP_NOT,
|
|
|
|
JS_OVOP_COUNT,
|
|
} JSOverloadableOperatorEnum;
|
|
|
|
typedef struct {
|
|
uint32_t operator_index;
|
|
JSObject *ops[JS_OVOP_BINARY_COUNT]; /* self operators */
|
|
} JSBinaryOperatorDefEntry;
|
|
|
|
typedef struct {
|
|
int count;
|
|
JSBinaryOperatorDefEntry *tab;
|
|
} JSBinaryOperatorDef;
|
|
|
|
typedef struct {
|
|
uint32_t operator_counter;
|
|
BOOL is_primitive; /* OperatorSet for a primitive type */
|
|
/* NULL if no operator is defined */
|
|
JSObject *self_ops[JS_OVOP_COUNT]; /* self operators */
|
|
JSBinaryOperatorDef left;
|
|
JSBinaryOperatorDef right;
|
|
} JSOperatorSetData;
|
|
|
|
typedef struct JSReqModuleEntry {
|
|
JSAtom module_name;
|
|
JSModuleDef *module; /* used using resolution */
|
|
} JSReqModuleEntry;
|
|
|
|
typedef enum JSExportTypeEnum {
|
|
JS_EXPORT_TYPE_LOCAL,
|
|
JS_EXPORT_TYPE_INDIRECT,
|
|
} JSExportTypeEnum;
|
|
|
|
typedef struct JSExportEntry {
|
|
union {
|
|
struct {
|
|
int var_idx; /* closure variable index */
|
|
JSVarRef *var_ref; /* if != NULL, reference to the variable */
|
|
} local; /* for local export */
|
|
int req_module_idx; /* module for indirect export */
|
|
} u;
|
|
JSExportTypeEnum export_type;
|
|
JSAtom local_name; /* '*' if export ns from. not used for local
|
|
export after compilation */
|
|
JSAtom export_name; /* exported variable name */
|
|
} JSExportEntry;
|
|
|
|
typedef struct JSStarExportEntry {
|
|
int req_module_idx; /* in req_module_entries */
|
|
} JSStarExportEntry;
|
|
|
|
typedef struct JSImportEntry {
|
|
int var_idx; /* closure variable index */
|
|
JSAtom import_name;
|
|
int req_module_idx; /* in req_module_entries */
|
|
} JSImportEntry;
|
|
|
|
struct JSModuleDef {
|
|
JSRefCountHeader header; /* must come first, 32-bit */
|
|
JSAtom module_name;
|
|
struct list_head link;
|
|
|
|
JSReqModuleEntry *req_module_entries;
|
|
int req_module_entries_count;
|
|
int req_module_entries_size;
|
|
|
|
JSExportEntry *export_entries;
|
|
int export_entries_count;
|
|
int export_entries_size;
|
|
|
|
JSStarExportEntry *star_export_entries;
|
|
int star_export_entries_count;
|
|
int star_export_entries_size;
|
|
|
|
JSImportEntry *import_entries;
|
|
int import_entries_count;
|
|
int import_entries_size;
|
|
|
|
JSValue module_ns;
|
|
JSValue func_obj; /* only used for JS modules */
|
|
JSModuleInitFunc *init_func; /* only used for C modules */
|
|
BOOL resolved : 8;
|
|
BOOL func_created : 8;
|
|
BOOL instantiated : 8;
|
|
BOOL evaluated : 8;
|
|
BOOL eval_mark : 8; /* temporary use during js_evaluate_module() */
|
|
/* true if evaluation yielded an exception. It is saved in
|
|
eval_exception */
|
|
BOOL eval_has_exception : 8;
|
|
JSValue eval_exception;
|
|
JSValue meta_obj; /* for import.meta */
|
|
};
|
|
|
|
typedef struct JSJobEntry {
|
|
struct list_head link;
|
|
JSContext *ctx;
|
|
JSJobFunc *job_func;
|
|
int argc;
|
|
JSValue argv[0];
|
|
} JSJobEntry;
|
|
|
|
typedef struct JSProperty {
|
|
union {
|
|
JSValue value; /* JS_PROP_NORMAL */
|
|
struct { /* JS_PROP_GETSET */
|
|
JSObject *getter; /* NULL if undefined */
|
|
JSObject *setter; /* NULL if undefined */
|
|
} getset;
|
|
JSVarRef *var_ref; /* JS_PROP_VARREF */
|
|
struct { /* JS_PROP_AUTOINIT */
|
|
/* in order to use only 2 pointers, we compress the realm
|
|
and the init function pointer */
|
|
uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x)
|
|
in the 2 low bits */
|
|
void *opaque;
|
|
} init;
|
|
} u;
|
|
} JSProperty;
|
|
|
|
#define JS_PROP_INITIAL_SIZE 2
|
|
#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */
|
|
#define JS_ARRAY_INITIAL_SIZE 2
|
|
|
|
typedef struct JSShapeProperty {
|
|
uint32_t hash_next : 26; /* 0 if last in list */
|
|
uint32_t flags : 6; /* JS_PROP_XXX */
|
|
JSAtom atom; /* JS_ATOM_NULL = free property entry */
|
|
} JSShapeProperty;
|
|
|
|
struct JSShape {
|
|
/* hash table of size hash_mask + 1 before the start of the
|
|
structure (see prop_hash_end()). */
|
|
JSGCObjectHeader header;
|
|
/* true if the shape is inserted in the shape hash table. If not,
|
|
JSShape.hash is not valid */
|
|
uint8_t is_hashed;
|
|
/* If true, the shape may have small array index properties 'n' with 0
|
|
<= n <= 2^31-1. If false, the shape is guaranteed not to have
|
|
small array index properties */
|
|
uint8_t has_small_array_index;
|
|
uint32_t hash; /* current hash value */
|
|
uint32_t prop_hash_mask;
|
|
int prop_size; /* allocated properties */
|
|
int prop_count; /* include deleted properties */
|
|
int deleted_prop_count;
|
|
JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */
|
|
JSObject *proto;
|
|
JSShapeProperty prop[0]; /* prop_size elements */
|
|
};
|
|
|
|
struct JSObject {
|
|
union {
|
|
JSGCObjectHeader header;
|
|
struct {
|
|
int __gc_ref_count; /* corresponds to header.ref_count */
|
|
uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */
|
|
|
|
uint8_t extensible : 1;
|
|
uint8_t free_mark : 1; /* only used when freeing objects with cycles */
|
|
uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */
|
|
uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */
|
|
uint8_t is_constructor : 1; /* TRUE if object is a constructor function */
|
|
uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */
|
|
uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */
|
|
uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */
|
|
uint16_t class_id; /* see JS_CLASS_x */
|
|
};
|
|
};
|
|
/* byte offsets: 16/24 */
|
|
JSShape *shape; /* prototype and property names + flag */
|
|
JSProperty *prop; /* array of properties */
|
|
/* byte offsets: 24/40 */
|
|
struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */
|
|
/* byte offsets: 28/48 */
|
|
union {
|
|
void *opaque;
|
|
struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */
|
|
struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */
|
|
struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */
|
|
struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */
|
|
struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */
|
|
#ifdef CONFIG_BIGNUM
|
|
struct JSFloatEnv *float_env; /* JS_CLASS_FLOAT_ENV */
|
|
struct JSOperatorSetData *operator_set; /* JS_CLASS_OPERATOR_SET */
|
|
#endif
|
|
struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */
|
|
struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */
|
|
struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */
|
|
struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */
|
|
struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */
|
|
struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */
|
|
struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */
|
|
struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */
|
|
struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */
|
|
struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
|
|
struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */
|
|
struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */
|
|
/* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */
|
|
struct JSFunctionBytecode *function_bytecode;
|
|
JSVarRef **var_refs;
|
|
JSObject *home_object; /* for 'super' access */
|
|
} func;
|
|
struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */
|
|
JSContext *realm;
|
|
JSCFunctionType c_function;
|
|
uint8_t length;
|
|
uint8_t cproto;
|
|
int16_t magic;
|
|
} cfunc;
|
|
/* array part for fast arrays and typed arrays */
|
|
struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */
|
|
union {
|
|
uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */
|
|
struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */
|
|
} u1;
|
|
union {
|
|
JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */
|
|
void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */
|
|
int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */
|
|
uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */
|
|
int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */
|
|
uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */
|
|
int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */
|
|
uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */
|
|
int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */
|
|
uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */
|
|
float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */
|
|
double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */
|
|
} u;
|
|
uint32_t count; /* <= 2^31-1. 0 for a detached typed array */
|
|
} array; /* 12/20 bytes */
|
|
JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */
|
|
JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */
|
|
} u;
|
|
/* byte sizes: 40/48/72 */
|
|
};
|
|
enum {
|
|
__JS_ATOM_NULL = JS_ATOM_NULL,
|
|
#define DEF(name, str) JS_ATOM_ ## name,
|
|
#include "quickjs-atom.h"
|
|
#undef DEF
|
|
JS_ATOM_END,
|
|
};
|
|
#define JS_ATOM_LAST_KEYWORD JS_ATOM_super
|
|
#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield
|
|
|
|
static const char js_atom_init[] =
|
|
#define DEF(name, str) str "\0"
|
|
#include "quickjs-atom.h"
|
|
#undef DEF
|
|
;
|
|
|
|
typedef enum OPCodeFormat {
|
|
#define FMT(f) OP_FMT_ ## f,
|
|
#define DEF(id, size, n_pop, n_push, f)
|
|
#include "quickjs-opcode.h"
|
|
#undef DEF
|
|
#undef FMT
|
|
} OPCodeFormat;
|
|
|
|
enum OPCodeEnum {
|
|
#define FMT(f)
|
|
#define DEF(id, size, n_pop, n_push, f) OP_ ## id,
|
|
#define def(id, size, n_pop, n_push, f)
|
|
#include "quickjs-opcode.h"
|
|
#undef def
|
|
#undef DEF
|
|
#undef FMT
|
|
OP_COUNT, /* excluding temporary opcodes */
|
|
/* temporary opcodes : overlap with the short opcodes */
|
|
OP_TEMP_START = OP_nop + 1,
|
|
OP___dummy = OP_TEMP_START - 1,
|
|
#define FMT(f)
|
|
#define DEF(id, size, n_pop, n_push, f)
|
|
#define def(id, size, n_pop, n_push, f) OP_ ## id,
|
|
#include "quickjs-opcode.h"
|
|
#undef def
|
|
#undef DEF
|
|
#undef FMT
|
|
OP_TEMP_END,
|
|
};
|
|
|
|
static int JS_InitAtoms(JSRuntime *rt);
|
|
static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
|
int atom_type);
|
|
static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p);
|
|
static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b);
|
|
static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj,
|
|
JSValueConst this_obj,
|
|
int argc, JSValueConst *argv, int flags);
|
|
static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj,
|
|
JSValueConst this_obj,
|
|
int argc, JSValueConst *argv, int flags);
|
|
static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj,
|
|
JSValueConst this_obj, JSValueConst new_target,
|
|
int argc, JSValue *argv, int flags);
|
|
static JSValue JS_CallConstructorInternal(JSContext *ctx,
|
|
JSValueConst func_obj,
|
|
JSValueConst new_target,
|
|
int argc, JSValue *argv, int flags);
|
|
static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj,
|
|
int argc, JSValueConst *argv);
|
|
static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom,
|
|
int argc, JSValueConst *argv);
|
|
static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen,
|
|
JSValue val, BOOL is_array_ctor);
|
|
static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj,
|
|
JSValueConst val, int flags, int scope_idx);
|
|
JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...);
|
|
static __maybe_unused void JS_DumpAtoms(JSRuntime *rt);
|
|
static __maybe_unused void JS_DumpString(JSRuntime *rt,
|
|
const JSString *p);
|
|
static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt);
|
|
static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p);
|
|
static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p);
|
|
static __maybe_unused void JS_DumpValueShort(JSRuntime *rt,
|
|
JSValueConst val);
|
|
static __maybe_unused void JS_DumpValue(JSContext *ctx, JSValueConst val);
|
|
static __maybe_unused void JS_PrintValue(JSContext *ctx,
|
|
const char *str,
|
|
JSValueConst val);
|
|
static __maybe_unused void JS_DumpShapes(JSRuntime *rt);
|
|
static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic);
|
|
static void js_array_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_array_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_object_data_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_object_data_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_c_function_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_c_function_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_bound_function_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_bound_function_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_regexp_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_typed_array_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_typed_array_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_proxy_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_proxy_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_map_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_map_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_generator_finalizer(JSRuntime *rt, JSValue obj);
|
|
static void js_generator_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_promise_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_promise_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
#ifdef CONFIG_BIGNUM
|
|
static void js_operator_set_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_operator_set_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
#endif
|
|
static JSValue JS_ToStringFree(JSContext *ctx, JSValue val);
|
|
static int JS_ToBoolFree(JSContext *ctx, JSValue val);
|
|
static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val);
|
|
static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val);
|
|
static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val);
|
|
static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
|
|
JSValueConst flags);
|
|
static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor,
|
|
JSValue pattern, JSValue bc);
|
|
static void gc_decref(JSRuntime *rt);
|
|
static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
|
|
const JSClassDef *class_def, JSAtom name);
|
|
|
|
typedef enum JSStrictEqModeEnum {
|
|
JS_EQ_STRICT,
|
|
JS_EQ_SAME_VALUE,
|
|
JS_EQ_SAME_VALUE_ZERO,
|
|
} JSStrictEqModeEnum;
|
|
|
|
static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2,
|
|
JSStrictEqModeEnum eq_mode);
|
|
static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2);
|
|
static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2);
|
|
static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2);
|
|
static JSValue JS_ToObject(JSContext *ctx, JSValueConst val);
|
|
static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val);
|
|
static JSProperty *add_property(JSContext *ctx,
|
|
JSObject *p, JSAtom prop, int prop_flags);
|
|
#ifdef CONFIG_BIGNUM
|
|
static void js_float_env_finalizer(JSRuntime *rt, JSValue val);
|
|
static JSValue JS_NewBigFloat(JSContext *ctx);
|
|
static inline bf_t *JS_GetBigFloat(JSValueConst val)
|
|
{
|
|
JSBigFloat *p = JS_VALUE_GET_PTR(val);
|
|
return &p->num;
|
|
}
|
|
static JSValue JS_NewBigDecimal(JSContext *ctx);
|
|
static inline bfdec_t *JS_GetBigDecimal(JSValueConst val)
|
|
{
|
|
JSBigDecimal *p = JS_VALUE_GET_PTR(val);
|
|
return &p->num;
|
|
}
|
|
static JSValue JS_NewBigInt(JSContext *ctx);
|
|
static inline bf_t *JS_GetBigInt(JSValueConst val)
|
|
{
|
|
JSBigFloat *p = JS_VALUE_GET_PTR(val);
|
|
return &p->num;
|
|
}
|
|
static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val,
|
|
BOOL convert_to_safe_integer);
|
|
static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val);
|
|
static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val);
|
|
static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val);
|
|
static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf);
|
|
static bf_t *JS_ToBigFloat(JSContext *ctx, bf_t *buf, JSValueConst val);
|
|
static JSValue JS_ToBigDecimalFree(JSContext *ctx, JSValue val,
|
|
BOOL allow_null_or_undefined);
|
|
static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val);
|
|
#endif
|
|
JSValue JS_ThrowOutOfMemory(JSContext *ctx);
|
|
static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx);
|
|
static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj);
|
|
static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj,
|
|
JSValueConst proto_val, BOOL throw_flag);
|
|
static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj);
|
|
static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj);
|
|
static int js_proxy_isArray(JSContext *ctx, JSValueConst obj);
|
|
static int JS_CreateProperty(JSContext *ctx, JSObject *p,
|
|
JSAtom prop, JSValueConst val,
|
|
JSValueConst getter, JSValueConst setter,
|
|
int flags);
|
|
static int js_string_memcmp(const JSString *p1, const JSString *p2, int len);
|
|
static void reset_weak_ref(JSRuntime *rt, JSObject *p);
|
|
static 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 BOOL typed_array_is_detached(JSContext *ctx, JSObject *p);
|
|
static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p);
|
|
static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx);
|
|
static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx,
|
|
BOOL is_arg);
|
|
static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj,
|
|
JSValueConst this_obj,
|
|
int argc, JSValueConst *argv,
|
|
int flags);
|
|
static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
|
|
const char *input, size_t input_len,
|
|
const char *filename, int flags, int scope_idx);
|
|
static void js_free_module_def(JSContext *ctx, JSModuleDef *m);
|
|
static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
|
|
JS_MarkFunc *mark_func);
|
|
static JSValue js_import_meta(JSContext *ctx);
|
|
static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier);
|
|
static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref);
|
|
static JSValue js_new_promise_capability(JSContext *ctx,
|
|
JSValue *resolving_funcs,
|
|
JSValueConst ctor);
|
|
static __exception int perform_promise_then(JSContext *ctx,
|
|
JSValueConst promise,
|
|
JSValueConst *resolve_reject,
|
|
JSValueConst *cap_resolving_funcs);
|
|
static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic);
|
|
static int js_string_compare(JSContext *ctx,
|
|
const JSString *p1, const JSString *p2);
|
|
static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val);
|
|
static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj,
|
|
JSValue prop, JSValue val, int flags);
|
|
static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val);
|
|
static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val);
|
|
static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val);
|
|
static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc,
|
|
JSObject *p, JSAtom prop);
|
|
static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc);
|
|
static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s,
|
|
JS_MarkFunc *mark_func);
|
|
static void JS_AddIntrinsicBasicObjects(JSContext *ctx);
|
|
static void js_free_shape(JSRuntime *rt, JSShape *sh);
|
|
static void js_free_shape_null(JSRuntime *rt, JSShape *sh);
|
|
static int js_shape_prepare_update(JSContext *ctx, JSObject *p,
|
|
JSShapeProperty **pprs);
|
|
static int init_shape_hash(JSRuntime *rt);
|
|
static __exception int js_get_length32(JSContext *ctx, uint32_t *pres,
|
|
JSValueConst obj);
|
|
static __exception int js_get_length64(JSContext *ctx, int64_t *pres,
|
|
JSValueConst obj);
|
|
static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len);
|
|
static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen,
|
|
JSValueConst array_arg);
|
|
static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj,
|
|
JSValue **arrpp, uint32_t *countp);
|
|
static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx,
|
|
JSValueConst sync_iter);
|
|
static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val);
|
|
static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func);
|
|
static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj,
|
|
JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int flags);
|
|
static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val);
|
|
static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h,
|
|
JSGCObjectTypeEnum type);
|
|
static void remove_gc_object(JSGCObjectHeader *h);
|
|
static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s);
|
|
static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque);
|
|
static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom,
|
|
void *opaque);
|
|
static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p,
|
|
JSAtom atom, void *opaque);
|
|
void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag);
|
|
|
|
static const JSClassExoticMethods js_arguments_exotic_methods;
|
|
static const JSClassExoticMethods js_string_exotic_methods;
|
|
static const JSClassExoticMethods js_proxy_exotic_methods;
|
|
static const JSClassExoticMethods js_module_ns_exotic_methods;
|
|
static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
|
|
|
|
static void js_trigger_gc(JSRuntime *rt, size_t size)
|
|
{
|
|
BOOL force_gc;
|
|
#ifdef FORCE_GC_AT_MALLOC
|
|
force_gc = TRUE;
|
|
#else
|
|
force_gc = ((rt->malloc_state.malloc_size + size) >
|
|
rt->malloc_gc_threshold);
|
|
#endif
|
|
if (force_gc) {
|
|
#ifdef DUMP_GC
|
|
printf("GC: size=%" PRIu64 "\n",
|
|
(uint64_t)rt->malloc_state.malloc_size);
|
|
#endif
|
|
JS_RunGC(rt);
|
|
rt->malloc_gc_threshold = rt->malloc_state.malloc_size +
|
|
(rt->malloc_state.malloc_size >> 1);
|
|
}
|
|
}
|
|
|
|
static size_t js_malloc_usable_size_unknown(const void *ptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void *js_malloc_rt(JSRuntime *rt, size_t size)
|
|
{
|
|
return rt->mf.js_malloc(&rt->malloc_state, size);
|
|
}
|
|
|
|
void js_free_rt(JSRuntime *rt, void *ptr)
|
|
{
|
|
rt->mf.js_free(&rt->malloc_state, ptr);
|
|
}
|
|
|
|
void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size)
|
|
{
|
|
return rt->mf.js_realloc(&rt->malloc_state, ptr, size);
|
|
}
|
|
|
|
size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr)
|
|
{
|
|
return rt->mf.js_malloc_usable_size(ptr);
|
|
}
|
|
|
|
void *js_mallocz_rt(JSRuntime *rt, size_t size)
|
|
{
|
|
void *ptr;
|
|
ptr = js_malloc_rt(rt, size);
|
|
if (!ptr)
|
|
return NULL;
|
|
return memset(ptr, 0, size);
|
|
}
|
|
|
|
#ifdef CONFIG_BIGNUM
|
|
/* called by libbf */
|
|
static void *js_bf_realloc(void *opaque, void *ptr, size_t size)
|
|
{
|
|
JSRuntime *rt = opaque;
|
|
return js_realloc_rt(rt, ptr, size);
|
|
}
|
|
#endif /* CONFIG_BIGNUM */
|
|
|
|
/* Throw out of memory in case of error */
|
|
void *js_malloc(JSContext *ctx, size_t size)
|
|
{
|
|
void *ptr;
|
|
ptr = js_malloc_rt(ctx->rt, size);
|
|
if (unlikely(!ptr)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
/* Throw out of memory in case of error */
|
|
void *js_mallocz(JSContext *ctx, size_t size)
|
|
{
|
|
void *ptr;
|
|
ptr = js_mallocz_rt(ctx->rt, size);
|
|
if (unlikely(!ptr)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
void js_free(JSContext *ctx, void *ptr)
|
|
{
|
|
js_free_rt(ctx->rt, ptr);
|
|
}
|
|
|
|
/* Throw out of memory in case of error */
|
|
void *js_realloc(JSContext *ctx, void *ptr, size_t size)
|
|
{
|
|
void *ret;
|
|
ret = js_realloc_rt(ctx->rt, ptr, size);
|
|
if (unlikely(!ret && size != 0)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* store extra allocated size in *pslack if successful */
|
|
void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack)
|
|
{
|
|
void *ret;
|
|
ret = js_realloc_rt(ctx->rt, ptr, size);
|
|
if (unlikely(!ret && size != 0)) {
|
|
JS_ThrowOutOfMemory(ctx);
|
|
return NULL;
|
|
}
|
|
if (pslack) {
|
|
size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret);
|
|
*pslack = (new_size > size) ? new_size - size : 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
size_t js_malloc_usable_size(JSContext *ctx, const void *ptr)
|
|
{
|
|
return js_malloc_usable_size_rt(ctx->rt, ptr);
|
|
}
|
|
|
|
/* Throw out of memory exception in case of error */
|
|
char *js_strndup(JSContext *ctx, const char *s, size_t n)
|
|
{
|
|
char *ptr;
|
|
ptr = js_malloc(ctx, n + 1);
|
|
if (ptr) {
|
|
memcpy(ptr, s, n);
|
|
ptr[n] = '\0';
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
char *js_strdup(JSContext *ctx, const char *str)
|
|
{
|
|
return js_strndup(ctx, str, strlen(str));
|
|
}
|
|
|
|
static no_inline int js_realloc_array(JSContext *ctx, void **parray,
|
|
int elem_size, int *psize, int req_size)
|
|
{
|
|
int new_size;
|
|
size_t slack;
|
|
void *new_array;
|
|
/* XXX: potential arithmetic overflow */
|
|
new_size = max_int(req_size, *psize * 3 / 2);
|
|
new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack);
|
|
if (!new_array)
|
|
return -1;
|
|
new_size += slack / elem_size;
|
|
*psize = new_size;
|
|
*parray = new_array;
|
|
return 0;
|
|
}
|
|
|
|
/* resize the array and update its size if req_size > *psize */
|
|
static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size,
|
|
int *psize, int req_size)
|
|
{
|
|
if (unlikely(req_size > *psize))
|
|
return js_realloc_array(ctx, parray, elem_size, psize, req_size);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static inline void js_dbuf_init(JSContext *ctx, DynBuf *s)
|
|
{
|
|
dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt);
|
|
}
|
|
|
|
static inline int is_digit(int c) {
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
typedef struct JSClassShortDef {
|
|
JSAtom class_name;
|
|
JSClassFinalizer *finalizer;
|
|
JSClassGCMark *gc_mark;
|
|
} JSClassShortDef;
|
|
|
|
static JSClassShortDef const js_std_class_def[] = {
|
|
{ JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */
|
|
{ JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */
|
|
{ JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */
|
|
{ JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */
|
|
{ JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */
|
|
{ JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */
|
|
{ JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */
|
|
{ JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */
|
|
{ JS_ATOM_Arguments, NULL, NULL }, /* JS_CLASS_MAPPED_ARGUMENTS */
|
|
{ JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */
|
|
{ JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */
|
|
{ JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */
|
|
{ JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */
|
|
{ JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */
|
|
{ JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */
|
|
{ JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */
|
|
{ JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */
|
|
{ JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */
|
|
{ JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */
|
|
{ JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */
|
|
{ JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */
|
|
{ JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */
|
|
{ JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */
|
|
{ JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */
|
|
{ JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */
|
|
{ JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */
|
|
{ JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */
|
|
#ifdef CONFIG_BIGNUM
|
|
{ JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */
|
|
{ JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */
|
|
#endif
|
|
{ JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */
|
|
{ JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */
|
|
{ JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */
|
|
#ifdef CONFIG_BIGNUM
|
|
{ JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */
|
|
{ JS_ATOM_BigFloat, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_FLOAT */
|
|
{ JS_ATOM_BigFloatEnv, js_float_env_finalizer, NULL }, /* JS_CLASS_FLOAT_ENV */
|
|
{ JS_ATOM_BigDecimal, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_DECIMAL */
|
|
{ JS_ATOM_OperatorSet, js_operator_set_finalizer, js_operator_set_mark }, /* JS_CLASS_OPERATOR_SET */
|
|
#endif
|
|
{ JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */
|
|
{ JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */
|
|
{ JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */
|
|
{ JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */
|
|
{ JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */
|
|
{ JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */
|
|
{ JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */
|
|
{ JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */
|
|
{ JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */
|
|
{ JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */
|
|
};
|
|
|
|
static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab,
|
|
int start, int count)
|
|
{
|
|
JSClassDef cm_s, *cm = &cm_s;
|
|
int i, class_id;
|
|
|
|
for(i = 0; i < count; i++) {
|
|
class_id = i + start;
|
|
memset(cm, 0, sizeof(*cm));
|
|
cm->finalizer = tab[i].finalizer;
|
|
cm->gc_mark = tab[i].gc_mark;
|
|
if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_BIGNUM
|
|
static JSValue JS_ThrowUnsupportedOperation(JSContext *ctx)
|
|
{
|
|
return JS_ThrowTypeError(ctx, "unsupported operation");
|
|
}
|
|
|
|
static JSValue invalid_to_string(JSContext *ctx, JSValueConst val)
|
|
{
|
|
return JS_ThrowUnsupportedOperation(ctx);
|
|
}
|
|
|
|
static JSValue invalid_from_string(JSContext *ctx, const char *buf,
|
|
int radix, int flags, slimb_t *pexponent)
|
|
{
|
|
return JS_NAN;
|
|
}
|
|
|
|
static int invalid_unary_arith(JSContext *ctx,
|
|
JSValue *pres, OPCodeEnum op, JSValue op1)
|
|
{
|
|
JS_FreeValue(ctx, op1);
|
|
JS_ThrowUnsupportedOperation(ctx);
|
|
return -1;
|
|
}
|
|
|
|
static int invalid_binary_arith(JSContext *ctx, OPCodeEnum op,
|
|
JSValue *pres, JSValue op1, JSValue op2)
|
|
{
|
|
JS_FreeValue(ctx, op1);
|
|
JS_FreeValue(ctx, op2);
|
|
JS_ThrowUnsupportedOperation(ctx);
|
|
return -1;
|
|
}
|
|
|
|
static JSValue invalid_mul_pow10_to_float64(JSContext *ctx, const bf_t *a,
|
|
int64_t exponent)
|
|
{
|
|
return JS_ThrowUnsupportedOperation(ctx);
|
|
}
|
|
|
|
static int invalid_mul_pow10(JSContext *ctx, JSValue *sp)
|
|
{
|
|
JS_ThrowUnsupportedOperation(ctx);
|
|
return -1;
|
|
}
|
|
|
|
static void set_dummy_numeric_ops(JSNumericOperations *ops)
|
|
{
|
|
ops->to_string = invalid_to_string;
|
|
ops->from_string = invalid_from_string;
|
|
ops->unary_arith = invalid_unary_arith;
|
|
ops->binary_arith = invalid_binary_arith;
|
|
ops->mul_pow10_to_float64 = invalid_mul_pow10_to_float64;
|
|
ops->mul_pow10 = invalid_mul_pow10;
|
|
}
|
|
|
|
#endif /* CONFIG_BIGNUM */
|
|
|
|
#if !defined(CONFIG_STACK_CHECK)
|
|
/* no stack limitation */
|
|
static inline uintptr_t js_get_stack_pointer(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size)
|
|
{
|
|
return FALSE;
|
|
}
|
|
#else
|
|
/* Note: OS and CPU dependent */
|
|
static inline uintptr_t js_get_stack_pointer(void)
|
|
{
|
|
return (uintptr_t)__builtin_frame_address(0);
|
|
}
|
|
|
|
static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size)
|
|
{
|
|
uintptr_t sp;
|
|
sp = js_get_stack_pointer() - alloca_size;
|
|
return unlikely(sp < rt->stack_limit);
|
|
}
|
|
#endif
|
|
|
|
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
|
|
{
|
|
JSRuntime *rt;
|
|
JSMallocState ms;
|
|
|
|
memset(&ms, 0, sizeof(ms));
|
|
ms.opaque = opaque;
|
|
ms.malloc_limit = -1;
|
|
|
|
rt = mf->js_malloc(&ms, sizeof(JSRuntime));
|
|
if (!rt)
|
|
return NULL;
|
|
memset(rt, 0, sizeof(*rt));
|
|
rt->mf = *mf;
|
|
if (!rt->mf.js_malloc_usable_size) {
|
|
/* use dummy function if none provided */
|
|
rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown;
|
|
}
|
|
rt->malloc_state = ms;
|
|
rt->malloc_gc_threshold = 256 * 1024;
|
|
|
|
#ifdef CONFIG_BIGNUM
|
|
bf_context_init(&rt->bf_ctx, js_bf_realloc, rt);
|
|
set_dummy_numeric_ops(&rt->bigint_ops);
|
|
set_dummy_numeric_ops(&rt->bigfloat_ops);
|
|
set_dummy_numeric_ops(&rt->bigdecimal_ops);
|
|
#endif
|
|
|
|
init_list_head(&rt->context_list);
|
|
init_list_head(&rt->gc_obj_list);
|
|
init_list_head(&rt->gc_zero_ref_count_list);
|
|
rt->gc_phase = JS_GC_PHASE_NONE;
|
|
|
|
#ifdef DUMP_LEAKS
|
|
init_list_head(&rt->string_list);
|
|
#endif
|
|
init_list_head(&rt->job_list);
|
|
|
|
if (JS_InitAtoms(rt))
|
|
goto fail;
|
|
|
|
/* create the object, array and function classes */
|
|
if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT,
|
|
countof(js_std_class_def)) < 0)
|
|
goto fail;
|
|
rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods;
|
|
rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods;
|
|
rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods;
|
|
|
|
rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function;
|
|
rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call;
|
|
rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function;
|
|
rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call;
|
|
if (init_shape_hash(rt))
|
|
goto fail;
|
|
|
|
rt->stack_size = JS_DEFAULT_STACK_SIZE;
|
|
JS_UpdateStackTop(rt);
|
|
|
|
rt->current_exception = JS_NULL;
|
|
|
|
return rt;
|
|
fail:
|
|
JS_FreeRuntime(rt);
|
|
return NULL;
|
|
}
|
|
|
|
void *JS_GetRuntimeOpaque(JSRuntime *rt)
|
|
{
|
|
return rt->user_opaque;
|
|
}
|
|
|
|
void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque)
|
|
{
|
|
rt->user_opaque = opaque;
|
|
}
|
|
|
|
/* default memory allocation functions with memory limitation */
|
|
static inline size_t js_def_malloc_usable_size(void *ptr)
|
|
{
|
|
#if defined(__APPLE__)
|
|
return malloc_size(ptr);
|
|
#elif defined(_WIN32)
|
|
return _msize(ptr);
|
|
#elif defined(EMSCRIPTEN)
|
|
return 0;
|
|
#elif defined(__linux__)
|
|
return malloc_usable_size(ptr);
|
|
#else
|
|
/* change this to `return 0;` if compilation fails */
|
|
return malloc_usable_size(ptr);
|
|
#endif
|
|
}
|
|
|
|
static void *js_def_malloc(JSMallocState *s, size_t size)
|
|
{
|
|
void *ptr;
|
|
|
|
/* Do not allocate zero bytes: behavior is platform dependent */
|
|
assert(size != 0);
|
|
|
|
if (unlikely(s->malloc_size + size > s->malloc_limit))
|
|
return NULL;
|
|
|
|
ptr = malloc(size);
|
|
if (!ptr)
|
|
return NULL;
|
|
|
|
s->malloc_count++;
|
|
s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
|
|
return ptr;
|
|
}
|
|
|
|
static void js_def_free(JSMallocState *s, void *ptr)
|
|
{
|
|
if (!ptr)
|
|
return;
|
|
|
|
s->malloc_count--;
|
|
s->malloc_size -= js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
|
|
free(ptr);
|
|
}
|
|
|
|
static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size)
|
|
{
|
|
size_t old_size;
|
|
|
|
if (!ptr) {
|
|
if (size == 0)
|
|
return NULL;
|
|
return js_def_malloc(s, size);
|
|
}
|
|
old_size = js_def_malloc_usable_size(ptr);
|
|
if (size == 0) {
|
|
s->malloc_count--;
|
|
s->malloc_size -= old_size + MALLOC_OVERHEAD;
|
|
free(ptr);
|
|
return NULL;
|
|
}
|
|
if (s->malloc_size + size - old_size > s->malloc_limit)
|
|
return NULL;
|
|
|
|
ptr = realloc(ptr, size);
|
|
if (!ptr)
|
|
return NULL;
|
|
|
|
s->malloc_size += js_def_malloc_usable_size(ptr) - old_size;
|
|
return ptr;
|
|
}
|
|
|
|
static const JSMallocFunctions def_malloc_funcs = {
|
|
js_def_malloc,
|
|
js_def_free,
|
|
js_def_realloc,
|
|
#if defined(__APPLE__)
|
|
malloc_size,
|
|
#elif defined(_WIN32)
|
|
(size_t (*)(const void *))_msize,
|
|
#elif defined(EMSCRIPTEN)
|
|
NULL,
|
|
#elif defined(__linux__)
|
|
(size_t (*)(const void *))malloc_usable_size,
|
|
#else
|
|
/* change this to `NULL,` if compilation fails */
|
|
malloc_usable_size,
|
|
#endif
|
|
};
|
|
|
|
JSRuntime *JS_NewRuntime(void)
|
|
{
|
|
return JS_NewRuntime2(&def_malloc_funcs, NULL);
|
|
}
|
|
|
|
void JS_SetMemoryLimit(JSRuntime *rt, size_t limit)
|
|
{
|
|
rt->malloc_state.malloc_limit = limit;
|
|
}
|
|
|
|
/* use -1 to disable automatic GC */
|
|
void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold)
|
|
{
|
|
rt->malloc_gc_threshold = gc_threshold;
|
|
}
|
|
|
|
#define malloc(s) malloc_is_forbidden(s)
|
|
#define free(p) free_is_forbidden(p)
|
|
#define realloc(p,s) realloc_is_forbidden(p,s)
|
|
|
|
void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque)
|
|
{
|
|
rt->interrupt_handler = cb;
|
|
rt->interrupt_opaque = opaque;
|
|
}
|
|
|
|
void JS_SetCanBlock(JSRuntime *rt, BOOL can_block)
|
|
{
|
|
rt->can_block = can_block;
|
|
}
|
|
|
|
void JS_SetSharedArrayBufferFunctions(JSRuntime *rt,
|
|
const JSSharedArrayBufferFunctions *sf)
|
|
{
|
|
rt->sab_funcs = *sf;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
JS_FreeValueRT(rt, rt->current_exception);
|
|
|
|
list_for_each_safe(el, el1, &rt->job_list) {
|
|
JSJobEntry *e = list_entry(el, JSJobEntry, link);
|
|
for(i = 0; i < e->argc; i++)
|
|
JS_FreeValueRT(rt, e->argv[i]);
|
|
js_free_rt(rt, e);
|
|
}
|
|
init_list_head(&rt->job_list);
|
|
|
|
JS_RunGC(rt);
|
|
|
|
#ifdef DUMP_LEAKS
|
|
/* leaking objects */
|
|
{
|
|
BOOL header_done;
|
|
JSGCObjectHeader *p;
|
|
int count;
|
|
|
|
/* remove the internal refcounts to display only the object
|
|
referenced externally */
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
p->mark = 0;
|
|
}
|
|
gc_decref(rt);
|
|
|
|
header_done = FALSE;
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
if (p->ref_count != 0) {
|
|
if (!header_done) {
|
|
printf("Object leaks:\n");
|
|
JS_DumpObjectHeader(rt);
|
|
header_done = TRUE;
|
|
}
|
|
JS_DumpGCObject(rt, p);
|
|
}
|
|
}
|
|
|
|
count = 0;
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
if (p->ref_count == 0) {
|
|
count++;
|
|
}
|
|
}
|
|
if (count != 0)
|
|
printf("Secondary object leaks: %d\n", count);
|
|
}
|
|
#endif
|
|
assert(list_empty(&rt->gc_obj_list));
|
|
|
|
/* free the classes */
|
|
for(i = 0; i < rt->class_count; i++) {
|
|
JSClass *cl = &rt->class_array[i];
|
|
if (cl->class_id != 0) {
|
|
JS_FreeAtomRT(rt, cl->class_name);
|
|
}
|
|
}
|
|
js_free_rt(rt, rt->class_array);
|
|
|
|
#ifdef CONFIG_BIGNUM
|
|
bf_context_end(&rt->bf_ctx);
|
|
#endif
|
|
|
|
#ifdef DUMP_LEAKS
|
|
/* only the atoms defined in JS_InitAtoms() should be left */
|
|
{
|
|
BOOL header_done = FALSE;
|
|
|
|
for(i = 0; i < rt->atom_size; i++) {
|
|
JSAtomStruct *p = rt->atom_array[i];
|
|
if (!atom_is_free(p) /* && p->str*/) {
|
|
if (i >= JS_ATOM_END || p->header.ref_count != 1) {
|
|
if (!header_done) {
|
|
header_done = TRUE;
|
|
if (rt->rt_info) {
|
|
printf("%s:1: atom leakage:", rt->rt_info);
|
|
} else {
|
|
printf("Atom leaks:\n"
|
|
" %6s %6s %s\n",
|
|
"ID", "REFCNT", "NAME");
|
|
}
|
|
}
|
|
if (rt->rt_info) {
|
|
printf(" ");
|
|
} else {
|
|
printf(" %6u %6u ", i, p->header.ref_count);
|
|
}
|
|
switch (p->atom_type) {
|
|
case JS_ATOM_TYPE_STRING:
|
|
JS_DumpString(rt, p);
|
|
break;
|
|
case JS_ATOM_TYPE_GLOBAL_SYMBOL:
|
|
printf("Symbol.for(");
|
|
JS_DumpString(rt, p);
|
|
printf(")");
|
|
break;
|
|
case JS_ATOM_TYPE_SYMBOL:
|
|
if (p->hash == JS_ATOM_HASH_SYMBOL) {
|
|
printf("Symbol(");
|
|
JS_DumpString(rt, p);
|
|
printf(")");
|
|
} else {
|
|
printf("Private(");
|
|
JS_DumpString(rt, p);
|
|
printf(")");
|
|
}
|
|
break;
|
|
}
|
|
if (rt->rt_info) {
|
|
printf(":%u", p->header.ref_count);
|
|
} else {
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (rt->rt_info && header_done)
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
/* free the atoms */
|
|
for(i = 0; i < rt->atom_size; i++) {
|
|
JSAtomStruct *p = rt->atom_array[i];
|
|
if (!atom_is_free(p)) {
|
|
#ifdef DUMP_LEAKS
|
|
list_del(&p->link);
|
|
#endif
|
|
js_free_rt(rt, p);
|
|
}
|
|
}
|
|
js_free_rt(rt, rt->atom_array);
|
|
js_free_rt(rt, rt->atom_hash);
|
|
js_free_rt(rt, rt->shape_hash);
|
|
#ifdef DUMP_LEAKS
|
|
if (!list_empty(&rt->string_list)) {
|
|
if (rt->rt_info) {
|
|
printf("%s:1: string leakage:", rt->rt_info);
|
|
} else {
|
|
printf("String leaks:\n"
|
|
" %6s %s\n",
|
|
"REFCNT", "VALUE");
|
|
}
|
|
list_for_each_safe(el, el1, &rt->string_list) {
|
|
JSString *str = list_entry(el, JSString, link);
|
|
if (rt->rt_info) {
|
|
printf(" ");
|
|
} else {
|
|
printf(" %6u ", str->header.ref_count);
|
|
}
|
|
JS_DumpString(rt, str);
|
|
if (rt->rt_info) {
|
|
printf(":%u", str->header.ref_count);
|
|
} else {
|
|
printf("\n");
|
|
}
|
|
list_del(&str->link);
|
|
js_free_rt(rt, str);
|
|
}
|
|
if (rt->rt_info)
|
|
printf("\n");
|
|
}
|
|
{
|
|
JSMallocState *s = &rt->malloc_state;
|
|
if (s->malloc_count > 1) {
|
|
if (rt->rt_info)
|
|
printf("%s:1: ", rt->rt_info);
|
|
printf("Memory leak: %"PRIu64" bytes lost in %"PRIu64" block%s\n",
|
|
(uint64_t)(s->malloc_size - sizeof(JSRuntime)),
|
|
(uint64_t)(s->malloc_count - 1), &"s"[s->malloc_count == 2]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
JSMallocState ms = rt->malloc_state;
|
|
rt->mf.js_free(&ms, rt);
|
|
}
|
|
}
|
|
|
|
JSContext *JS_NewContextRaw(JSRuntime *rt)
|
|
{
|
|
JSContext *ctx;
|
|
int i;
|
|
|
|
ctx = js_mallocz_rt(rt, sizeof(JSContext));
|
|
if (!ctx)
|
|
return NULL;
|
|
ctx->header.ref_count = 1;
|
|
add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT);
|
|
|
|
ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) *
|
|
rt->class_count);
|
|
if (!ctx->class_proto) {
|
|
js_free_rt(rt, ctx);
|
|
return NULL;
|
|
}
|
|
ctx->rt = rt;
|
|
list_add_tail(&ctx->link, &rt->context_list);
|
|
#ifdef CONFIG_BIGNUM
|
|
ctx->bf_ctx = &rt->bf_ctx;
|
|
ctx->fp_env.prec = 113;
|
|
ctx->fp_env.flags = bf_set_exp_bits(15) | BF_RNDN | BF_FLAG_SUBNORMAL;
|
|
#endif
|
|
for(i = 0; i < rt->class_count; i++)
|
|
ctx->class_proto[i] = JS_NULL;
|
|
ctx->array_ctor = JS_NULL;
|
|
ctx->regexp_ctor = JS_NULL;
|
|
ctx->promise_ctor = JS_NULL;
|
|
init_list_head(&ctx->loaded_modules);
|
|
|
|
JS_AddIntrinsicBasicObjects(ctx);
|
|
return ctx;
|
|
}
|
|
|
|
JSContext *JS_NewContext(JSRuntime *rt)
|
|
{
|
|
JSContext *ctx;
|
|
|
|
ctx = JS_NewContextRaw(rt);
|
|
if (!ctx)
|
|
return NULL;
|
|
|
|
JS_AddIntrinsicBaseObjects(ctx);
|
|
JS_AddIntrinsicDate(ctx);
|
|
JS_AddIntrinsicEval(ctx);
|
|
JS_AddIntrinsicStringNormalize(ctx);
|
|
JS_AddIntrinsicRegExp(ctx);
|
|
JS_AddIntrinsicJSON(ctx);
|
|
JS_AddIntrinsicProxy(ctx);
|
|
JS_AddIntrinsicMapSet(ctx);
|
|
JS_AddIntrinsicTypedArrays(ctx);
|
|
JS_AddIntrinsicPromise(ctx);
|
|
#ifdef CONFIG_BIGNUM
|
|
JS_AddIntrinsicBigInt(ctx);
|
|
#endif
|
|
return ctx;
|
|
}
|
|
|
|
void *JS_GetContextOpaque(JSContext *ctx)
|
|
{
|
|
return ctx->user_opaque;
|
|
}
|
|
|
|
void JS_SetContextOpaque(JSContext *ctx, void *opaque)
|
|
{
|
|
ctx->user_opaque = opaque;
|
|
}
|
|
|
|
/* set the new value and free the old value after (freeing the value
|
|
can reallocate the object data) */
|
|
static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val)
|
|
{
|
|
JSValue old_val;
|
|
old_val = *pval;
|
|
*pval = new_val;
|
|
JS_FreeValue(ctx, old_val);
|
|
}
|
|
|
|
void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
assert(class_id < rt->class_count);
|
|
set_value(ctx, &ctx->class_proto[class_id], obj);
|
|
}
|
|
|
|
JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
assert(class_id < rt->class_count);
|
|
return JS_DupValue(ctx, ctx->class_proto[class_id]);
|
|
}
|
|
|
|
typedef enum JSFreeModuleEnum {
|
|
JS_FREE_MODULE_ALL,
|
|
JS_FREE_MODULE_NOT_RESOLVED,
|
|
JS_FREE_MODULE_NOT_EVALUATED,
|
|
} JSFreeModuleEnum;
|
|
|
|
/* XXX: would be more efficient with separate module lists */
|
|
static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag)
|
|
{
|
|
struct list_head *el, *el1;
|
|
list_for_each_safe(el, el1, &ctx->loaded_modules) {
|
|
JSModuleDef *m = list_entry(el, JSModuleDef, link);
|
|
if (flag == JS_FREE_MODULE_ALL ||
|
|
(flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved) ||
|
|
(flag == JS_FREE_MODULE_NOT_EVALUATED && !m->evaluated)) {
|
|
js_free_module_def(ctx, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void JS_FreeContext(JSContext *ctx)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
int i;
|
|
|
|
if (--ctx->header.ref_count > 0)
|
|
return;
|
|
assert(ctx->header.ref_count == 0);
|
|
|
|
#ifdef DUMP_ATOMS
|
|
JS_DumpAtoms(ctx->rt);
|
|
#endif
|
|
#ifdef DUMP_SHAPES
|
|
JS_DumpShapes(ctx->rt);
|
|
#endif
|
|
#ifdef DUMP_OBJECTS
|
|
{
|
|
struct list_head *el;
|
|
JSGCObjectHeader *p;
|
|
printf("JSObjects: {\n");
|
|
JS_DumpObjectHeader(ctx->rt);
|
|
list_for_each(el, &rt->gc_obj_list) {
|
|
p = list_entry(el, JSGCObjectHeader, link);
|
|
JS_DumpGCObject(rt, p);
|
|
}
|
|
printf("}\n");
|
|
}
|
|
#endif
|
|
#ifdef DUMP_MEM
|
|
{
|
|
JSMemoryUsage stats;
|
|
JS_ComputeMemoryUsage(rt, &stats);
|
|
JS_DumpMemoryUsage(stdout, &stats, rt);
|
|
}
|
|
#endif
|
|
|
|
js_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);
|
|
JS_FreeValue(ctx, ctx->array_ctor);
|
|
JS_FreeValue(ctx, ctx->regexp_ctor);
|
|
JS_FreeValue(ctx, ctx->function_ctor);
|
|
JS_FreeValue(ctx, ctx->function_proto);
|
|
|
|
js_free_shape_null(ctx->rt, ctx->array_shape);
|
|
|
|
list_del(&ctx->link);
|
|
remove_gc_object(&ctx->header);
|
|
js_free_rt(ctx->rt, ctx);
|
|
}
|
|
|
|
JSRuntime *JS_GetRuntime(JSContext *ctx)
|
|
{
|
|
return ctx->rt;
|
|
}
|
|
|
|
static void update_stack_limit(JSRuntime *rt)
|
|
{
|
|
if (rt->stack_size == 0) {
|
|
rt->stack_limit = 0; /* no limit */
|
|
} else {
|
|
rt->stack_limit = rt->stack_top - rt->stack_size;
|
|
}
|
|
}
|
|
|
|
void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size)
|
|
{
|
|
rt->stack_size = stack_size;
|
|
update_stack_limit(rt);
|
|
}
|
|
|
|
void JS_UpdateStackTop(JSRuntime *rt)
|
|
{
|
|
rt->stack_top = js_get_stack_pointer();
|
|
update_stack_limit(rt);
|
|
}
|
|
|
|
static inline BOOL is_strict_mode(JSContext *ctx)
|
|
{
|
|
JSStackFrame *sf = ctx->rt->current_stack_frame;
|
|
return (sf && (sf->js_mode & JS_MODE_STRICT));
|
|
}
|
|
|
|
#ifdef CONFIG_BIGNUM
|
|
static inline BOOL is_math_mode(JSContext *ctx)
|
|
{
|
|
JSStackFrame *sf = ctx->rt->current_stack_frame;
|
|
return (sf && (sf->js_mode & JS_MODE_MATH));
|
|
}
|
|
#endif
|
|
|
|
/* JSAtom support */
|
|
|
|
#define JS_ATOM_TAG_INT (1U << 31)
|
|
#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1)
|
|
#define JS_ATOM_MAX ((1U << 30) - 1)
|
|
|
|
/* return the max count from the hash size */
|
|
#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2)
|
|
|
|
static inline BOOL __JS_AtomIsConst(JSAtom v)
|
|
{
|
|
#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1
|
|
return (int32_t)v <= 0;
|
|
#else
|
|
return (int32_t)v < JS_ATOM_END;
|
|
#endif
|
|
}
|
|
|
|
static inline BOOL __JS_AtomIsTaggedInt(JSAtom v)
|
|
{
|
|
return (v & JS_ATOM_TAG_INT) != 0;
|
|
}
|
|
|
|
static inline JSAtom __JS_AtomFromUInt32(uint32_t v)
|
|
{
|
|
return v | JS_ATOM_TAG_INT;
|
|
}
|
|
|
|
static inline uint32_t __JS_AtomToUInt32(JSAtom atom)
|
|
{
|
|
return atom & ~JS_ATOM_TAG_INT;
|
|
}
|
|
|
|
static inline int is_num(int c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
/* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */
|
|
static inline BOOL is_num_string(uint32_t *pval, const JSString *p)
|
|
{
|
|
uint32_t n;
|
|
uint64_t n64;
|
|
int c, i, len;
|
|
|
|
len = p->len;
|
|
if (len == 0 || len > 10)
|
|
return FALSE;
|
|
if (p->is_wide_char)
|
|
c = p->u.str16[0];
|
|
else
|
|
c = p->u.str8[0];
|
|
if (is_num(c)) {
|
|
if (c == '0') {
|
|
if (len != 1)
|
|
return FALSE;
|
|
n = 0;
|
|
} else {
|
|
n = c - '0';
|
|
for(i = 1; i < len; i++) {
|
|
if (p->is_wide_char)
|
|
c = p->u.str16[i];
|
|
else
|
|
c = p->u.str8[i];
|
|
if (!is_num(c))
|
|
return FALSE;
|
|
n64 = (uint64_t)n * 10 + (c - '0');
|
|
if ((n64 >> 32) != 0)
|
|
return FALSE;
|
|
n = n64;
|
|
}
|
|
}
|
|
*pval = n;
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* XXX: could use faster version ? */
|
|
static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h)
|
|
{
|
|
size_t i;
|
|
|
|
for(i = 0; i < len; i++)
|
|
h = h * 263 + str[i];
|
|
return h;
|
|
}
|
|
|
|
static inline uint32_t hash_string16(const uint16_t *str,
|
|
size_t len, uint32_t h)
|
|
{
|
|
size_t i;
|
|
|
|
for(i = 0; i < len; i++)
|
|
h = h * 263 + str[i];
|
|
return h;
|
|
}
|
|
|
|
static uint32_t hash_string(const JSString *str, uint32_t h)
|
|
{
|
|
if (str->is_wide_char)
|
|
h = hash_string16(str->u.str16, str->len, h);
|
|
else
|
|
h = hash_string8(str->u.str8, str->len, h);
|
|
return h;
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpString(JSRuntime *rt,
|
|
const JSString *p)
|
|
{
|
|
int i, c, sep;
|
|
|
|
if (p == NULL) {
|
|
printf("<null>");
|
|
return;
|
|
}
|
|
printf("%d", p->header.ref_count);
|
|
sep = (p->header.ref_count == 1) ? '\"' : '\'';
|
|
putchar(sep);
|
|
for(i = 0; i < p->len; i++) {
|
|
if (p->is_wide_char)
|
|
c = p->u.str16[i];
|
|
else
|
|
c = p->u.str8[i];
|
|
if (c == sep || c == '\\') {
|
|
putchar('\\');
|
|
putchar(c);
|
|
} else if (c >= ' ' && c <= 126) {
|
|
putchar(c);
|
|
} else if (c == '\n') {
|
|
putchar('\\');
|
|
putchar('n');
|
|
} else {
|
|
printf("\\u%04x", c);
|
|
}
|
|
}
|
|
putchar(sep);
|
|
}
|
|
|
|
static __maybe_unused void JS_DumpAtoms(JSRuntime *rt)
|
|
{
|
|
JSAtomStruct *p;
|
|
int h, i;
|
|
/* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */
|
|
printf("JSAtom count=%d size=%d hash_size=%d:\n",
|
|
rt->atom_count, rt->atom_size, rt->atom_hash_size);
|
|
printf("JSAtom hash table: {\n");
|
|
for(i = 0; i < rt->atom_hash_size; i++) {
|
|
h = rt->atom_hash[i];
|
|
if (h) {
|
|
printf(" %d:", i);
|
|
while (h) {
|
|
p = rt->atom_array[h];
|
|
printf(" ");
|
|
JS_DumpString(rt, p);
|
|
h = p->hash_next;
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
printf("}\n");
|
|
printf("JSAtom table: {\n");
|
|
for(i = 0; i < rt->atom_size; i++) {
|
|
p = rt->atom_array[i];
|
|
if (!atom_is_free(p)) {
|
|
printf(" %d: { %d %08x ", i, p->atom_type, p->hash);
|
|
if (!(p->len == 0 && p->is_wide_char != 0))
|
|
JS_DumpString(rt, p);
|
|
printf(" %d }\n", p->hash_next);
|
|
}
|
|
}
|
|
printf("}\n");
|
|
}
|
|
|
|
static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size)
|
|
{
|
|
JSAtomStruct *p;
|
|
uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash;
|
|
|
|
assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */
|
|
new_hash_mask = new_hash_size - 1;
|
|
new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size);
|
|
if (!new_hash)
|
|
return -1;
|
|
for(i = 0; i < rt->atom_hash_size; i++) {
|
|
h = rt->atom_hash[i];
|
|
while (h != 0) {
|
|
p = rt->atom_array[h];
|
|
hash_next1 = p->hash_next;
|
|
/* add in new hash table */
|
|
j = p->hash & new_hash_mask;
|
|
p->hash_next = new_hash[j];
|
|
new_hash[j] = h;
|
|
h = hash_next1;
|
|
}
|
|
}
|
|
js_free_rt(rt, rt->atom_hash);
|
|
rt->atom_hash = new_hash;
|
|
rt->atom_hash_size = new_hash_size;
|
|
rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size);
|
|
// JS_DumpAtoms(rt);
|
|
return 0;
|
|
}
|
|
|
|
static int JS_InitAtoms(JSRuntime *rt)
|
|
{
|
|
int i, len, atom_type;
|
|
const char *p;
|
|
|
|
rt->atom_hash_size = 0;
|
|
rt->atom_hash = NULL;
|
|
rt->atom_count = 0;
|
|
rt->atom_size = 0;
|
|
rt->atom_free_index = 0;
|
|
if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */
|
|
return -1;
|
|
|
|
p = js_atom_init;
|
|
for(i = 1; i < JS_ATOM_END; i++) {
|
|
if (i == JS_ATOM_Private_brand)
|
|
atom_type = JS_ATOM_TYPE_PRIVATE;
|
|
else if (i >= JS_ATOM_Symbol_toPrimitive)
|
|
atom_type = JS_ATOM_TYPE_SYMBOL;
|
|
else
|
|
atom_type = JS_ATOM_TYPE_STRING;
|
|
len = strlen(p);
|
|
if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL)
|
|
return -1;
|
|
p = p + len + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v)
|
|
{
|
|
JSAtomStruct *p;
|
|
|
|
if (!__JS_AtomIsConst(v)) {
|
|
p = rt->atom_array[v];
|
|
p->header.ref_count++;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
JSAtom JS_DupAtom(JSContext *ctx, JSAtom v)
|
|
{
|
|
JSRuntime *rt;
|
|
JSAtomStruct *p;
|
|
|
|
if (!__JS_AtomIsConst(v)) {
|
|
rt = ctx->rt;
|
|
p = rt->atom_array[v];
|
|
p->header.ref_count++;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v)
|
|
{
|
|
JSRuntime *rt;
|
|
JSAtomStruct *p;
|
|
|
|
rt = ctx->rt;
|
|
if (__JS_AtomIsTaggedInt(v))
|
|
return JS_ATOM_KIND_STRING;
|
|
p = rt->atom_array[v];
|
|
switch(p->atom_type) {
|
|
case JS_ATOM_TYPE_STRING:
|
|
return JS_ATOM_KIND_STRING;
|
|
case JS_ATOM_TYPE_GLOBAL_SYMBOL:
|
|
return JS_ATOM_KIND_SYMBOL;
|
|
case JS_ATOM_TYPE_SYMBOL:
|
|
switch(p->hash) {
|
|
case JS_ATOM_HASH_SYMBOL:
|
|
return JS_ATOM_KIND_SYMBOL;
|
|
case JS_ATOM_HASH_PRIVATE:
|
|
return JS_ATOM_KIND_PRIVATE;
|
|
default:
|
|
abort();
|
|
}
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static BOOL JS_AtomIsString(JSContext *ctx, JSAtom v)
|
|
{
|
|
return JS_AtomGetKind(ctx, v) == JS_ATOM_KIND_STRING;
|
|
}
|
|
|
|
static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p)
|
|
{
|
|
uint32_t i = p->hash_next; /* atom_index */
|
|
if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
|
|
JSAtomStruct *p1;
|
|
|
|
i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)];
|
|
p1 = rt->atom_array[i];
|
|
while (p1 != p) {
|
|
assert(i != 0);
|
|
i = p1->hash_next;
|
|
p1 = rt->atom_array[i];
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* string case (internal). Return JS_ATOM_NULL if error. 'str' is
|
|
freed. */
|
|
static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|
{
|
|
uint32_t h, h1, i;
|
|
JSAtomStruct *p;
|
|
int len;
|
|
|
|
#if 0
|
|
printf("__JS_NewAtom: "); JS_DumpString(rt, str); printf("\n");
|
|
#endif
|
|
if (atom_type < JS_ATOM_TYPE_SYMBOL) {
|
|
/* str is not NULL */
|
|
if (str->atom_type == atom_type) {
|
|
/* str is the atom, return its index */
|
|
i = js_get_atom_index(rt, str);
|
|
/* reduce string refcount and increase atom's unless constant */
|
|
if (__JS_AtomIsConst(i))
|
|
str->header.ref_count--;
|
|
return i;
|
|
}
|
|
/* try and locate an already registered atom */
|
|
len = str->len;
|
|
h = hash_string(str, atom_type);
|
|
h &= JS_ATOM_HASH_MASK;
|
|
h1 = h & (rt->atom_hash_size - 1);
|
|
i = rt->atom_hash[h1];
|
|
while (i != 0) {
|
|
p = rt->atom_array[i];
|
|
if (p->hash == h &&
|
|
p->atom_type == atom_type &&
|
|
p->len == len &&
|
|
js_string_memcmp(p, str, len) == 0) {
|
|
if (!__JS_AtomIsConst(i))
|
|
p->header.ref_count++;
|
|
goto done;
|
|
}
|
|
i = p->hash_next;
|
|
}
|
|
} else {
|
|
h1 = 0; /* avoid warning */
|
|
if (atom_type == JS_ATOM_TYPE_SYMBOL) {
|
|
h = JS_ATOM_HASH_SYMBOL;
|
|
} else {
|
|
h = JS_ATOM_HASH_PRIVATE;
|
|
atom_type = JS_ATOM_TYPE_SYMBOL;
|
|
}
|
|
}
|
|
|
|
if (rt->atom_free_index == 0) {
|
|
/* allow new atom entries */
|
|
uint32_t new_size, start;
|
|
JSAtomStruct **new_array;
|
|
|
|
/* alloc new with size progression 3/2:
|
|
4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092
|
|
preallocating space for predefined atoms (at least 195).
|
|
*/
|
|
new_size = max_int(211, rt->atom_size * 3 / 2);
|
|
if (new_size > JS_ATOM_MAX)
|
|
goto fail;
|
|
/* XXX: should use realloc2 to use slack space */
|
|
new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size);
|
|
if (!new_array)
|
|
goto fail;
|
|
/* Note: the atom 0 is not used */
|
|
start = rt->atom_size;
|
|
if (start == 0) {
|
|
/* JS_ATOM_NULL entry */
|
|
p = js_mallocz_rt(rt, sizeof(JSAtomStruct));
|
|
if (!p) {
|
|
js_free_rt(rt, new_array);
|
|
goto fail;
|
|
}
|
|
p->header.ref_count = 1; /* not refcounted */
|
|
p->atom_type = JS_ATOM_TYPE_SYMBOL;
|
|
#ifdef DUMP_LEAKS
|
|
list_add_tail(&p->link, &rt->string_list);
|
|
#endif
|
|
new_array[0] = p;
|
|
rt->atom_count++;
|
|
start = 1;
|
|
}
|
|
rt->atom_size = new_size;
|
|
rt->atom_array = new_array;
|
|
rt->atom_free_index = start;
|
|
for(i = start; i < new_size; i++) {
|
|
uint32_t next;
|
|
if (i == (new_size - 1))
|
|
next = 0;
|
|
else
|
|
next = i + 1;
|
|
rt->atom_array[i] = atom_set_free(next);
|
|
}
|
|
}
|
|
|
|
if (str) {
|
|
if (str->atom_type == 0) {
|
|
p = str;
|
|
p->atom_type = atom_type;
|
|
} else {
|
|
p = js_malloc_rt(rt, sizeof(JSString) +
|
|
(str->len << str->is_wide_char) +
|
|
1 - str->is_wide_char);
|
|
if (unlikely(!p))
|
|
goto fail;
|
|
p->header.ref_count = 1;
|
|
p->is_wide_char = str->is_wide_char;
|
|
p->len = str->len;
|
|
#ifdef DUMP_LEAKS
|
|
list_add_tail(&p->link, &rt->string_list);
|
|
#endif
|
|
memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) +
|
|
1 - str->is_wide_char);
|
|
js_free_string(rt, str);
|
|
}
|
|
} else {
|
|
p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */
|
|
if (!p)
|
|
return JS_ATOM_NULL;
|
|
p->header.ref_count = 1;
|
|
p->is_wide_char = 1; /* Hack to represent NULL as a JSString */
|
|
p->len = 0;
|
|
#ifdef DUMP_LEAKS
|
|
list_add_tail(&p->link, &rt->string_list);
|
|
#endif
|
|
}
|
|
|
|
/* use an already free entry */
|
|
i = rt->atom_free_index;
|
|
rt->atom_free_index = atom_get_free(rt->atom_array[i]);
|
|
rt->atom_array[i] = p;
|
|
|
|
p->hash = h;
|
|
p->hash_next = i; /* atom_index */
|
|
p->atom_type = atom_type;
|
|
|
|
rt->atom_count++;
|
|
|
|
if (atom_type != JS_ATOM_TYPE_SYMBOL) {
|
|
p->hash_next = rt->atom_hash[h1];
|
|
rt->atom_hash[h1] = i;
|
|
if (unlikely(rt->atom_count >= rt->atom_count_resize))
|
|
JS_ResizeAtomHash(rt, rt->atom_hash_size * 2);
|
|
}
|
|
|
|
// JS_DumpAtoms(rt);
|
|
return i;
|
|
|
|
fail:
|
|
i = JS_ATOM_NULL;
|
|
done:
|
|
if (str)
|
|
js_free_string(rt, str);
|
|
return i;
|
|
}
|
|
|
|
/* only works with zero terminated 8 bit strings */
|
|
static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
|
int atom_type)
|
|
{
|
|
JSString *p;
|
|
p = js_alloc_string_rt(rt, len, 0);
|
|
if (!p)
|
|
return JS_ATOM_NULL;
|
|
memcpy(p->u.str8, str, len);
|
|
p->u.str8[len] = '\0';
|
|
return __JS_NewAtom(rt, p, atom_type);
|
|
}
|
|
|
|
static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len,
|
|
int atom_type)
|
|
{
|
|
uint32_t h, h1, i;
|
|
JSAtomStruct *p;
|
|
|
|
h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING);
|
|
h &= JS_ATOM_HASH_MASK;
|
|
h1 = h & (rt->atom_hash_size - 1);
|
|
i = rt->atom_hash[h1];
|
|
while (i != 0) {
|
|
p = rt->atom_array[i];
|
|
if (p->hash == h &&
|
|
p->atom_type == JS_ATOM_TYPE_STRING &&
|
|
p->len == len &&
|
|
p->is_wide_char == 0 &&
|
|
memcmp(p->u.str8, str, len) == 0) {
|
|
if (!__JS_AtomIsConst(i))
|
|
p->header.ref_count++;
|
|
return i;
|
|
}
|
|
i = p->hash_next;
|
|
}
|
|
return JS_ATOM_NULL;
|
|
}
|
|
|
|
static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p)
|
|
{
|
|
#if 0 /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */
|
|
if (unlikely(i == JS_ATOM_NULL)) {
|
|
p->header.ref_count = INT32_MAX / 2;
|
|
return;
|
|
}
|
|
#endif
|
|
uint32_t i = p->hash_next; /* atom_index */
|
|
if (p->atom_type != JS_ATOM_TYPE_SYMBOL) {
|
|
JSAtomStruct *p0, *p1;
|
|
uint32_t h0;
|
|
|
|
h0 = p->hash & (rt->atom_hash_size - 1);
|
|
i = rt->atom_hash[h0];
|
|
p1 = rt->atom_array[i];
|
|
if (p1 == p) {
|
|
rt->atom_hash[h0] = p1->hash_next;
|
|
} else {
|
|
for(;;) {
|
|
assert(i != 0);
|
|
p0 = p1;
|
|
i = p1->hash_next;
|
|
p1 = rt->atom_array[i];
|
|
if (p1 == p) {
|
|
p0->hash_next = p1->hash_next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* insert in free atom list */
|
|
rt->atom_array[i] = atom_set_free(rt->atom_free_index);
|
|
rt->atom_free_index = i;
|
|
/* free the string structure */
|
|
#ifdef DUMP_LEAKS
|
|
list_del(&p->link);
|
|
#endif
|
|
js_free_rt(rt, p);
|
|
rt->atom_count--;
|
|
assert(rt->atom_count >= 0);
|
|
}
|
|
|
|
static void __JS_FreeAtom(JSRuntime *rt, uint32_t i)
|
|
{
|
|
JSAtomStruct *p;
|
|
|
|
p = rt->atom_array[i];
|
|
if (--p->header.ref_count > 0)
|
|
return;
|
|
JS_FreeAtomStruct(rt, p);
|
|
}
|
|
|
|
/* Warning: 'p' is freed */
|
|
static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
uint32_t n;
|
|
if (is_num_string(&n, p)) {
|
|
if (n <= JS_ATOM_MAX_INT) {
|
|
js_free_string(rt, p);
|
|
return __JS_AtomFromUInt32(n);
|
|
}
|
|
}
|
|
/* XXX: should generate an exception */
|
|
return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING);
|
|
}
|
|
|
|
JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len)
|
|
{
|
|
JSValue val;
|
|
|
|
if (len == 0 || !is_digit(*str)) {
|
|
JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING);
|
|
if (atom)
|
|
return atom;
|
|
}
|
|
val = JS_NewStringLen(ctx, str, len);
|
|
if (JS_IsException(val))
|
|
return JS_ATOM_NULL;
|
|
return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val));
|
|
}
|
|
|
|
JSAtom JS_NewAtom(JSContext *ctx, const char *str)
|
|
{
|
|
return JS_NewAtomLen(ctx, str, strlen(str));
|
|
}
|
|
|
|
JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n)
|
|
{
|
|
if (n <= JS_ATOM_MAX_INT) {
|
|
return __JS_AtomFromUInt32(n);
|
|
} else {
|
|
char buf[11];
|
|
JSValue val;
|
|
snprintf(buf, sizeof(buf), "%u", n);
|
|
val = JS_NewString(ctx, buf);
|
|
if (JS_IsException(val))
|
|
return JS_ATOM_NULL;
|
|
return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val),
|
|
JS_ATOM_TYPE_STRING);
|
|
}
|
|
}
|
|
|
|
static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n)
|
|
{
|
|
if ((uint64_t)n <= JS_ATOM_MAX_INT) {
|
|
return __JS_AtomFromUInt32((uint32_t)n);
|
|
} else {
|
|
char buf[24];
|
|
JSValue val;
|
|
snprintf(buf, sizeof(buf), "%" PRId64 , n);
|
|
val = JS_NewString(ctx, buf);
|
|
if (JS_IsException(val))
|
|
return JS_ATOM_NULL;
|
|
return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val),
|
|
JS_ATOM_TYPE_STRING);
|
|
}
|
|
}
|
|
|
|
/* 'p' is freed */
|
|
static JSValue JS_NewSymbol(JSContext *ctx, JSString *p, int atom_type)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSAtom atom;
|
|
atom = __JS_NewAtom(rt, p, atom_type);
|
|
if (atom == JS_ATOM_NULL)
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]);
|
|
}
|
|
|
|
/* descr must be a non-numeric string atom */
|
|
static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr,
|
|
int atom_type)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSString *p;
|
|
|
|
assert(!__JS_AtomIsTaggedInt(descr));
|
|
assert(descr < rt->atom_size);
|
|
p = rt->atom_array[descr];
|
|
JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
|
|
return JS_NewSymbol(ctx, p, atom_type);
|
|
}
|
|
|
|
#define ATOM_GET_STR_BUF_SIZE 64
|
|
|
|
/* Should only be used for debug. */
|
|
static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size,
|
|
JSAtom atom)
|
|
{
|
|
if (__JS_AtomIsTaggedInt(atom)) {
|
|
snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom));
|
|
} else {
|
|
JSAtomStruct *p;
|
|
assert(atom < rt->atom_size);
|
|
if (atom == JS_ATOM_NULL) {
|
|
snprintf(buf, buf_size, "<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++) {
|
|
if (str->is_wide_char)
|
|
c = str->u.str16[i];
|
|
else
|
|
c = str->u.str8[i];
|
|
if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX)
|
|
break;
|
|
if (c < 128) {
|
|
*q++ = c;
|
|
} else {
|
|
q += unicode_to_utf8((uint8_t *)q, c);
|
|
}
|
|
}
|
|
}
|
|
*q = '\0';
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom)
|
|
{
|
|
return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom);
|
|
}
|
|
|
|
static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
|
|
if (__JS_AtomIsTaggedInt(atom)) {
|
|
snprintf(buf, sizeof(buf), "%u", __JS_AtomToUInt32(atom));
|
|
return JS_NewString(ctx, buf);
|
|
} else {
|
|
JSRuntime *rt = ctx->rt;
|
|
JSAtomStruct *p;
|
|
assert(atom < rt->atom_size);
|
|
p = rt->atom_array[atom];
|
|
if (p->atom_type == JS_ATOM_TYPE_STRING) {
|
|
goto ret_string;
|
|
} else if (force_string) {
|
|
if (p->len == 0 && p->is_wide_char != 0) {
|
|
/* no description string */
|
|
p = rt->atom_array[JS_ATOM_empty_string];
|
|
}
|
|
ret_string:
|
|
return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
|
|
} else {
|
|
return JS_DupValue(ctx, JS_MKPTR(JS_TAG_SYMBOL, p));
|
|
}
|
|
}
|
|
}
|
|
|
|
JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom)
|
|
{
|
|
return __JS_AtomToValue(ctx, atom, FALSE);
|
|
}
|
|
|
|
JSValue JS_AtomToString(JSContext *ctx, JSAtom atom)
|
|
{
|
|
return __JS_AtomToValue(ctx, atom, TRUE);
|
|
}
|
|
|
|
/* return TRUE if the atom is an array index (i.e. 0 <= index <=
|
|
2^32-2 and return its value */
|
|
static BOOL JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom)
|
|
{
|
|
if (__JS_AtomIsTaggedInt(atom)) {
|
|
*pval = __JS_AtomToUInt32(atom);
|
|
return TRUE;
|
|
} else {
|
|
JSRuntime *rt = ctx->rt;
|
|
JSAtomStruct *p;
|
|
uint32_t val;
|
|
|
|
assert(atom < rt->atom_size);
|
|
p = rt->atom_array[atom];
|
|
if (p->atom_type == JS_ATOM_TYPE_STRING &&
|
|
is_num_string(&val, p) && val != -1) {
|
|
*pval = val;
|
|
return TRUE;
|
|
} else {
|
|
*pval = 0;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This test must be fast if atom is not a numeric index (e.g. a
|
|
method name). Return JS_UNDEFINED if not a numeric
|
|
index. JS_EXCEPTION can also be returned. */
|
|
static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom)
|
|
{
|
|
JSRuntime *rt = ctx->rt;
|
|
JSAtomStruct *p1;
|
|
JSString *p;
|
|
int c, len, ret;
|
|
JSValue num, str;
|
|
|
|
if (__JS_AtomIsTaggedInt(atom))
|
|
return JS_NewInt32(ctx, __JS_AtomToUInt32(atom));
|
|
assert(atom < rt->atom_size);
|
|
p1 = rt->atom_array[atom];
|
|
if (p1->atom_type != JS_ATOM_TYPE_STRING)
|
|
return JS_UNDEFINED;
|
|
p = p1;
|
|
len = p->len;
|
|
if (p->is_wide_char) {
|
|
const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len;
|
|
if (r >= r_end)
|
|
return JS_UNDEFINED;
|
|
c = *r;
|
|
if (c == '-') {
|
|
if (r >= r_end)
|
|
return JS_UNDEFINED;
|
|
r++;
|
|
c = *r;
|
|
/* -0 case is specific */
|
|
if (c == '0' && len == 2)
|
|
goto minus_zero;
|
|
}
|
|
/* XXX: should test NaN, but the tests do not check it */
|
|
if (!is_num(c)) {
|
|
/* XXX: String should be normalized, therefore 8-bit only */
|
|
const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' };
|
|
if (!(c =='I' && (r_end - r) == 8 &&
|
|
!memcmp(r + 1, nfinity16, sizeof(nfinity16))))
|
|
return JS_UNDEFINED;
|
|
}
|
|
} else {
|
|
const uint8_t *r = p->u.str8, *r_end = p->u.str8 + len;
|
|
if (r >= r_end)
|
|
return JS_UNDEFINED;
|
|
c = *r;
|
|
if (c == '-') {
|
|
if (r >= r_end)
|
|
return JS_UNDEFINED;
|
|
r++;
|
|
c = *r;
|
|
/* -0 case is specific */
|
|
if (c == '0' && len == 2) {
|
|
minus_zero:
|
|
return __JS_NewFloat64(ctx, -0.0);
|
|
}
|
|
}
|
|
if (!is_num(c)) {
|
|
if (!(c =='I' && (r_end - r) == 8 &&
|
|
!memcmp(r + 1, "nfinity", 7)))
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
/* XXX: bignum: would be better to only accept integer to avoid
|
|
relying on current floating point precision */
|
|
/* this is ECMA CanonicalNumericIndexString primitive */
|
|
num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p));
|
|
if (JS_IsException(num))
|
|
return num;
|
|
str = JS_ToString(ctx, num);
|
|
if (JS_IsException(str)) {
|
|
JS_FreeValue(ctx, num);
|
|
return str;
|
|
}
|
|
ret = js_string_compare(ctx, p, JS_VALUE_GET_STRING(str));
|
|
JS_FreeValue(ctx, str);
|
|
if (ret == 0) {
|
|
return num;
|
|
} else {
|
|
JS_FreeValue(ctx, num);
|
|
return JS_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
/* return -1 if exception or TRUE/FALSE */
|
|
static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom)
|
|
{
|
|
JSValue num;
|
|
num = JS_AtomIsNumericIndex1(ctx, atom);
|
|
if (likely(JS_IsUndefined(num)))
|
|
return FALSE;
|
|
if (JS_IsException(num))
|
|
return -1;
|
|
JS_FreeValue(ctx, num);
|
|
return TRUE;
|
|
}
|
|
|
|
void JS_FreeAtom(JSContext *ctx, JSAtom v)
|
|
{
|
|
if (!__JS_AtomIsConst(v))
|
|
__JS_FreeAtom(ctx->rt, v);
|
|
}
|
|
|
|
void JS_FreeAtomRT(JSRuntime *rt, JSAtom v)
|
|
{
|
|
if (!__JS_AtomIsConst(v))
|
|
__JS_FreeAtom(rt, v);
|
|
}
|
|
|
|
/* return TRUE if 'v' is a symbol with a string description */
|
|
static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v)
|
|
{
|
|
JSRuntime *rt;
|
|
JSAtomStruct *p;
|
|
|
|
rt = ctx->rt;
|
|
if (__JS_AtomIsTaggedInt(v))
|
|
return FALSE;
|
|
p = rt->atom_array[v];
|
|
return (((p->atom_type == JS_ATOM_TYPE_SYMBOL &&
|
|
p->hash == JS_ATOM_HASH_SYMBOL) ||
|
|
p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) &&
|
|
!(p->len == 0 && p->is_wide_char != 0));
|
|
}
|
|
|
|
static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom)
|
|
{
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
const char *p;
|
|
int i;
|
|
|
|
/* XXX: should handle embedded null characters */
|
|
/* XXX: should move encoding code to JS_AtomGetStr */
|
|
p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom);
|
|
for (i = 0; p[i]; i++) {
|
|
int c = (unsigned char)p[i];
|
|
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
|
(c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0)))
|
|
break;
|
|
}
|
|
if (i > 0 && p[i] == '\0') {
|
|
printf("%s", p);
|
|
} else {
|
|
putchar('"');
|
|
printf("%.*s", i, p);
|
|
for (; p[i]; i++) {
|
|
int c = (unsigned char)p[i];
|
|
if (c == '\"' || c == '\\') {
|
|
putchar('\\');
|
|
putchar(c);
|
|
} else if (c >= ' ' && c <= 126) {
|
|
putchar(c);
|
|
} else if (c == '\n') {
|
|
putchar('\\');
|
|
putchar('n');
|
|
} else {
|
|
printf("\\u%04x", c);
|
|
}
|
|
}
|
|
putchar('\"');
|
|
}
|
|
}
|
|
|
|
/* free with JS_FreeCString() */
|
|
const char *JS_AtomToCString(JSContext *ctx, JSAtom atom)
|
|
{
|
|
JSValue str;
|
|
const char *cstr;
|
|
|
|
str = JS_AtomToString(ctx, atom);
|
|
if (JS_IsException(str))
|
|
return NULL;
|
|
cstr = JS_ToCString(ctx, str);
|
|
JS_FreeValue(ctx, str);
|
|
return cstr;
|
|
}
|
|
|
|
/* return a string atom containing name concatenated with str1 */
|
|
static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1)
|
|
{
|
|
JSValue str;
|
|
JSAtom atom;
|
|
const char *cstr;
|
|
char *cstr2;
|
|
size_t len, len1;
|
|
|
|
str = JS_AtomToString(ctx, name);
|
|
if (JS_IsException(str))
|
|
return JS_ATOM_NULL;
|
|
cstr = JS_ToCStringLen(ctx, &len, str);
|
|
if (!cstr)
|
|
goto fail;
|
|
len1 = strlen(str1);
|
|
cstr2 = js_malloc(ctx, len + len1 + 1);
|
|
if (!cstr2)
|
|
goto fail;
|
|
memcpy(cstr2, cstr, len);
|
|
memcpy(cstr2 + len, str1, len1);
|
|
cstr2[len + len1] = '\0';
|
|
atom = JS_NewAtomLen(ctx, cstr2, len + len1);
|
|
js_free(ctx, cstr2);
|
|
JS_FreeCString(ctx, cstr);
|
|
JS_FreeValue(ctx, str);
|
|
return atom;
|
|
fail:
|
|
JS_FreeCString(ctx, cstr);
|
|
JS_FreeValue(ctx, str);
|
|
return JS_ATOM_NULL;
|
|
}
|
|
|
|
static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n)
|
|
{
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%u", n);
|
|
return js_atom_concat_str(ctx, name, buf);
|
|
}
|
|
|
|
static inline BOOL JS_IsEmptyString(JSValueConst v)
|
|
{
|
|
return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0;
|
|
}
|
|
|
|
/* JSClass support */
|
|
|
|
/* a new class ID is allocated if *pclass_id != 0 */
|
|
JSClassID JS_NewClassID(JSClassID *pclass_id)
|
|
{
|
|
JSClassID class_id;
|
|
/* XXX: make it thread safe */
|
|
class_id = *pclass_id;
|
|
if (class_id == 0) {
|
|
class_id = js_class_id_alloc++;
|
|
*pclass_id = class_id;
|
|
}
|
|
return class_id;
|
|
}
|
|
|
|
BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id)
|
|
{
|
|
return (class_id < rt->class_count &&
|
|
rt->class_array[class_id].class_id != 0);
|
|
}
|
|
|
|
/* create a new object internal class. Return -1 if error, 0 if
|
|
OK. The finalizer can be NULL if none is needed. */
|
|
static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
|
|
const JSClassDef *class_def, JSAtom name)
|
|
{
|
|
int new_size, i;
|
|
JSClass *cl, *new_class_array;
|
|
struct list_head *el;
|
|
|
|
if (class_id >= (1 << 16))
|
|
return -1;
|
|
if (class_id < rt->class_count &&
|
|
rt->class_array[class_id].class_id != 0)
|
|
return -1;
|
|
|
|
if (class_id >= rt->class_count) {
|
|
new_size = max_int(JS_CLASS_INIT_COUNT,
|
|
max_int(class_id + 1, rt->class_count * 3 / 2));
|
|
|
|
/* reallocate the context class prototype array, if any */
|
|
list_for_each(el, &rt->context_list) {
|
|
JSContext *ctx = list_entry(el, JSContext, link);
|
|
JSValue *new_tab;
|
|
new_tab = js_realloc_rt(rt, ctx->class_proto,
|
|
sizeof(ctx->class_proto[0]) * new_size);
|
|
if (!new_tab)
|
|
return -1;
|
|
for(i = rt->class_count; i < new_size; i++)
|
|
new_tab[i] = JS_NULL;
|
|
ctx->class_proto = new_tab;
|
|
}
|
|
/* reallocate the class array */
|
|
new_class_array = js_realloc_rt(rt, rt->class_array,
|
|
sizeof(JSClass) * new_size);
|
|
if (!new_class_array)
|
|
return -1;
|
|
memset(new_class_array + rt->class_count, 0,
|
|
(new_size - rt->class_count) * sizeof(JSClass));
|
|
rt->class_array = new_class_array;
|
|
rt->class_count = new_size;
|
|
}
|
|
cl = &rt->class_array[class_id];
|
|
cl->class_id = class_id;
|
|
cl->class_name = JS_DupAtomRT(rt, name);
|
|
cl->finalizer = class_def->finalizer;
|
|
cl->gc_mark = class_def->gc_mark;
|
|
cl->call = class_def->call;
|
|
cl->exotic = class_def->exotic;
|
|
return 0;
|
|
}
|
|
|
|
int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def)
|
|
{
|
|
int ret, len;
|
|
JSAtom name;
|
|
|
|
len = strlen(class_def->class_name);
|
|
name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING);
|
|
if (name == JS_ATOM_NULL) {
|
|
name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING);
|
|
if (name == JS_ATOM_NULL)
|
|
return -1;
|
|
}
|
|
ret = JS_NewClass1(rt, class_id, class_def, name);
|
|
JS_FreeAtomRT(rt, name);
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_new_string8(JSContext *ctx, const uint8_t *buf, int len)
|
|
{
|
|
JSString *str;
|
|
|
|
if (len <= 0) {
|
|
return JS_AtomToString(ctx, JS_ATOM_empty_string);
|
|
}
|
|
str = js_alloc_string(ctx, len, 0);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
memcpy(str->u.str8, buf, len);
|
|
str->u.str8[len] = '\0';
|
|
return JS_MKPTR(JS_TAG_STRING, str);
|
|
}
|
|
|
|
static JSValue js_new_string16(JSContext *ctx, const uint16_t *buf, int len)
|
|
{
|
|
JSString *str;
|
|
str = js_alloc_string(ctx, len, 1);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
memcpy(str->u.str16, buf, len * 2);
|
|
return JS_MKPTR(JS_TAG_STRING, str);
|
|
}
|
|
|
|
static JSValue js_new_string_char(JSContext *ctx, uint16_t c)
|
|
{
|
|
if (c < 0x100) {
|
|
uint8_t ch8 = c;
|
|
return js_new_string8(ctx, &ch8, 1);
|
|
} else {
|
|
uint16_t ch16 = c;
|
|
return js_new_string16(ctx, &ch16, 1);
|
|
}
|
|
}
|
|
|
|
static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end)
|
|
{
|
|
int len = end - start;
|
|
if (start == 0 && end == p->len) {
|
|
return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p));
|
|
}
|
|
if (p->is_wide_char && len > 0) {
|
|
JSString *str;
|
|
int i;
|
|
uint16_t c = 0;
|
|
for (i = start; i < end; i++) {
|
|
c |= p->u.str16[i];
|
|
}
|
|
if (c > 0xFF)
|
|
return js_new_string16(ctx, p->u.str16 + start, len);
|
|
|
|
str = js_alloc_string(ctx, len, 0);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
for (i = 0; i < len; i++) {
|
|
str->u.str8[i] = p->u.str16[start + i];
|
|
}
|
|
str->u.str8[len] = '\0';
|
|
return JS_MKPTR(JS_TAG_STRING, str);
|
|
} else {
|
|
return js_new_string8(ctx, p->u.str8 + start, len);
|
|
}
|
|
}
|
|
|
|
typedef struct StringBuffer {
|
|
JSContext *ctx;
|
|
JSString *str;
|
|
int len;
|
|
int size;
|
|
int is_wide_char;
|
|
int error_status;
|
|
} StringBuffer;
|
|
|
|
/* It is valid to call string_buffer_end() and all string_buffer functions even
|
|
if string_buffer_init() or another string_buffer function returns an error.
|
|
If the error_status is set, string_buffer_end() returns JS_EXCEPTION.
|
|
*/
|
|
static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size,
|
|
int is_wide)
|
|
{
|
|
s->ctx = ctx;
|
|
s->size = size;
|
|
s->len = 0;
|
|
s->is_wide_char = is_wide;
|
|
s->error_status = 0;
|
|
s->str = js_alloc_string(ctx, size, is_wide);
|
|
if (unlikely(!s->str)) {
|
|
s->size = 0;
|
|
return s->error_status = -1;
|
|
}
|
|
#ifdef DUMP_LEAKS
|
|
/* the StringBuffer may reallocate the JSString, only link it at the end */
|
|
list_del(&s->str->link);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size)
|
|
{
|
|
return string_buffer_init2(ctx, s, size, 0);
|
|
}
|
|
|
|
static void string_buffer_free(StringBuffer *s)
|
|
{
|
|
js_free(s->ctx, s->str);
|
|
s->str = NULL;
|
|
}
|
|
|
|
static int string_buffer_set_error(StringBuffer *s)
|
|
{
|
|
js_free(s->ctx, s->str);
|
|
s->str = NULL;
|
|
s->size = 0;
|
|
s->len = 0;
|
|
return s->error_status = -1;
|
|
}
|
|
|
|
static no_inline int string_buffer_widen(StringBuffer *s, int size)
|
|
{
|
|
JSString *str;
|
|
size_t slack;
|
|
int i;
|
|
|
|
if (s->error_status)
|
|
return -1;
|
|
|
|
str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack);
|
|
if (!str)
|
|
return string_buffer_set_error(s);
|
|
size += slack >> 1;
|
|
for(i = s->len; i-- > 0;) {
|
|
str->u.str16[i] = str->u.str8[i];
|
|
}
|
|
s->is_wide_char = 1;
|
|
s->size = size;
|
|
s->str = str;
|
|
return 0;
|
|
}
|
|
|
|
static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c)
|
|
{
|
|
JSString *new_str;
|
|
int new_size;
|
|
size_t new_size_bytes, slack;
|
|
|
|
if (s->error_status)
|
|
return -1;
|
|
|
|
if (new_len > JS_STRING_LEN_MAX) {
|
|
JS_ThrowInternalError(s->ctx, "string too long");
|
|
return string_buffer_set_error(s);
|
|
}
|
|
new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX);
|
|
if (!s->is_wide_char && c >= 0x100) {
|
|
return string_buffer_widen(s, new_size);
|
|
}
|
|
new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char;
|
|
new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack);
|
|
if (!new_str)
|
|
return string_buffer_set_error(s);
|
|
new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX);
|
|
s->size = new_size;
|
|
s->str = new_str;
|
|
return 0;
|
|
}
|
|
|
|
static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (unlikely(s->len >= s->size)) {
|
|
if (string_buffer_realloc(s, s->len + 1, c))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
s->str->u.str16[s->len++] = c;
|
|
} else if (c < 0x100) {
|
|
s->str->u.str8[s->len++] = c;
|
|
} else {
|
|
if (string_buffer_widen(s, s->size))
|
|
return -1;
|
|
s->str->u.str16[s->len++] = c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 0 <= c <= 0xff */
|
|
static int string_buffer_putc8(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (unlikely(s->len >= s->size)) {
|
|
if (string_buffer_realloc(s, s->len + 1, c))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
s->str->u.str16[s->len++] = c;
|
|
} else {
|
|
s->str->u.str8[s->len++] = c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 0 <= c <= 0xffff */
|
|
static int string_buffer_putc16(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (likely(s->len < s->size)) {
|
|
if (s->is_wide_char) {
|
|
s->str->u.str16[s->len++] = c;
|
|
return 0;
|
|
} else if (c < 0x100) {
|
|
s->str->u.str8[s->len++] = c;
|
|
return 0;
|
|
}
|
|
}
|
|
return string_buffer_putc_slow(s, c);
|
|
}
|
|
|
|
/* 0 <= c <= 0x10ffff */
|
|
static int string_buffer_putc(StringBuffer *s, uint32_t c)
|
|
{
|
|
if (unlikely(c >= 0x10000)) {
|
|
/* surrogate pair */
|
|
c -= 0x10000;
|
|
if (string_buffer_putc16(s, (c >> 10) + 0xd800))
|
|
return -1;
|
|
c = (c & 0x3ff) + 0xdc00;
|
|
}
|
|
return string_buffer_putc16(s, c);
|
|
}
|
|
|
|
static int string_get(const JSString *p, int idx) {
|
|
return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx];
|
|
}
|
|
|
|
static int string_getc(const JSString *p, int *pidx)
|
|
{
|
|
int idx, c, c1;
|
|
idx = *pidx;
|
|
if (p->is_wide_char) {
|
|
c = p->u.str16[idx++];
|
|
if (c >= 0xd800 && c < 0xdc00 && idx < p->len) {
|
|
c1 = p->u.str16[idx];
|
|
if (c1 >= 0xdc00 && c1 < 0xe000) {
|
|
c = (((c & 0x3ff) << 10) | (c1 & 0x3ff)) + 0x10000;
|
|
idx++;
|
|
}
|
|
}
|
|
} else {
|
|
c = p->u.str8[idx++];
|
|
}
|
|
*pidx = idx;
|
|
return c;
|
|
}
|
|
|
|
static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len)
|
|
{
|
|
int i;
|
|
|
|
if (s->len + len > s->size) {
|
|
if (string_buffer_realloc(s, s->len + len, 0))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
for (i = 0; i < len; i++) {
|
|
s->str->u.str16[s->len + i] = p[i];
|
|
}
|
|
s->len += len;
|
|
} else {
|
|
memcpy(&s->str->u.str8[s->len], p, len);
|
|
s->len += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len)
|
|
{
|
|
int c = 0, i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
c |= p[i];
|
|
}
|
|
if (s->len + len > s->size) {
|
|
if (string_buffer_realloc(s, s->len + len, c))
|
|
return -1;
|
|
} else if (!s->is_wide_char && c >= 0x100) {
|
|
if (string_buffer_widen(s, s->size))
|
|
return -1;
|
|
}
|
|
if (s->is_wide_char) {
|
|
memcpy(&s->str->u.str16[s->len], p, len << 1);
|
|
s->len += len;
|
|
} else {
|
|
for (i = 0; i < len; i++) {
|
|
s->str->u.str8[s->len + i] = p[i];
|
|
}
|
|
s->len += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* appending an ASCII string */
|
|
static int string_buffer_puts8(StringBuffer *s, const char *str)
|
|
{
|
|
return string_buffer_write8(s, (const uint8_t *)str, strlen(str));
|
|
}
|
|
|
|
static int string_buffer_concat(StringBuffer *s, const JSString *p,
|
|
uint32_t from, uint32_t to)
|
|
{
|
|
if (to <= from)
|
|
return 0;
|
|
if (p->is_wide_char)
|
|
return string_buffer_write16(s, p->u.str16 + from, to - from);
|
|
else
|
|
return string_buffer_write8(s, p->u.str8 + from, to - from);
|
|
}
|
|
|
|
static int string_buffer_concat_value(StringBuffer *s, JSValueConst v)
|
|
{
|
|
JSString *p;
|
|
JSValue v1;
|
|
int res;
|
|
|
|
if (s->error_status) {
|
|
/* prevent exception overload */
|
|
return -1;
|
|
}
|
|
if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) {
|
|
v1 = JS_ToString(s->ctx, v);
|
|
if (JS_IsException(v1))
|
|
return string_buffer_set_error(s);
|
|
p = JS_VALUE_GET_STRING(v1);
|
|
res = string_buffer_concat(s, p, 0, p->len);
|
|
JS_FreeValue(s->ctx, v1);
|
|
return res;
|
|
}
|
|
p = JS_VALUE_GET_STRING(v);
|
|
return string_buffer_concat(s, p, 0, p->len);
|
|
}
|
|
|
|
static int string_buffer_concat_value_free(StringBuffer *s, JSValue v)
|
|
{
|
|
JSString *p;
|
|
int res;
|
|
|
|
if (s->error_status) {
|
|
/* prevent exception overload */
|
|
JS_FreeValue(s->ctx, v);
|
|
return -1;
|
|
}
|
|
if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) {
|
|
v = JS_ToStringFree(s->ctx, v);
|
|
if (JS_IsException(v))
|
|
return string_buffer_set_error(s);
|
|
}
|
|
p = JS_VALUE_GET_STRING(v);
|
|
res = string_buffer_concat(s, p, 0, p->len);
|
|
JS_FreeValue(s->ctx, v);
|
|
return res;
|
|
}
|
|
|
|
static int string_buffer_fill(StringBuffer *s, int c, int count)
|
|
{
|
|
/* XXX: optimize */
|
|
if (s->len + count > s->size) {
|
|
if (string_buffer_realloc(s, s->len + count, c))
|
|
return -1;
|
|
}
|
|
while (count-- > 0) {
|
|
if (string_buffer_putc16(s, c))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static JSValue string_buffer_end(StringBuffer *s)
|
|
{
|
|
JSString *str;
|
|
str = s->str;
|
|
if (s->error_status)
|
|
return JS_EXCEPTION;
|
|
if (s->len == 0) {
|
|
js_free(s->ctx, str);
|
|
s->str = NULL;
|
|
return JS_AtomToString(s->ctx, JS_ATOM_empty_string);
|
|
}
|
|
if (s->len < s->size) {
|
|
/* smaller size so js_realloc should not fail, but OK if it does */
|
|
/* XXX: should add some slack to avoid unnecessary calls */
|
|
/* XXX: might need to use malloc+free to ensure smaller size */
|
|
str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) +
|
|
(s->len << s->is_wide_char) + 1 - s->is_wide_char);
|
|
if (str == NULL)
|
|
str = s->str;
|
|
s->str = str;
|
|
}
|
|
if (!s->is_wide_char)
|
|
str->u.str8[s->len] = 0;
|
|
#ifdef DUMP_LEAKS
|
|
list_add_tail(&str->link, &s->ctx->rt->string_list);
|
|
#endif
|
|
str->is_wide_char = s->is_wide_char;
|
|
str->len = s->len;
|
|
s->str = NULL;
|
|
return JS_MKPTR(JS_TAG_STRING, str);
|
|
}
|
|
|
|
/* create a string from a UTF-8 buffer */
|
|
JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len)
|
|
{
|
|
const uint8_t *p, *p_end, *p_start, *p_next;
|
|
uint32_t c;
|
|
StringBuffer b_s, *b = &b_s;
|
|
size_t len1;
|
|
|
|
p_start = (const uint8_t *)buf;
|
|
p_end = p_start + buf_len;
|
|
p = p_start;
|
|
while (p < p_end && *p < 128)
|
|
p++;
|
|
len1 = p - p_start;
|
|
if (len1 > JS_STRING_LEN_MAX)
|
|
return JS_ThrowInternalError(ctx, "string too long");
|
|
if (p == p_end) {
|
|
/* ASCII string */
|
|
return js_new_string8(ctx, (const uint8_t *)buf, buf_len);
|
|
} else {
|
|
if (string_buffer_init(ctx, b, buf_len))
|
|
goto fail;
|
|
string_buffer_write8(b, p_start, len1);
|
|
while (p < p_end) {
|
|
if (*p < 128) {
|
|
string_buffer_putc8(b, *p++);
|
|
} else {
|
|
/* parse utf-8 sequence, return 0xFFFFFFFF for error */
|
|
c = unicode_from_utf8(p, p_end - p, &p_next);
|
|
if (c < 0x10000) {
|
|
p = p_next;
|
|
} else if (c <= 0x10FFFF) {
|
|
p = p_next;
|
|
/* surrogate pair */
|
|
c -= 0x10000;
|
|
string_buffer_putc16(b, (c >> 10) + 0xd800);
|
|
c = (c & 0x3ff) + 0xdc00;
|
|
} else {
|
|
/* invalid char */
|
|
c = 0xfffd;
|
|
/* skip the invalid chars */
|
|
/* XXX: seems incorrect. Why not just use c = *p++; ? */
|
|
while (p < p_end && (*p >= 0x80 && *p < 0xc0))
|
|
p++;
|
|
if (p < p_end) {
|
|
p++;
|
|
while (p < p_end && (*p >= 0x80 && *p < 0xc0))
|
|
p++;
|
|
}
|
|
}
|
|
string_buffer_putc16(b, c);
|
|
}
|
|
}
|
|
}
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
string_buffer_free(b);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue JS_ConcatString3(JSContext *ctx, const char *str1,
|
|
JSValue str2, const char *str3)
|
|
{
|
|
StringBuffer b_s, *b = &b_s;
|
|
int len1, len3;
|
|
JSString *p;
|
|
|
|
if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) {
|
|
str2 = JS_ToStringFree(ctx, str2);
|
|
if (JS_IsException(str2))
|
|
goto fail;
|
|
}
|
|
p = JS_VALUE_GET_STRING(str2);
|
|
len1 = strlen(str1);
|
|
len3 = strlen(str3);
|
|
|
|
if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char))
|
|
goto fail;
|
|
|
|
string_buffer_write8(b, (const uint8_t *)str1, len1);
|
|
string_buffer_concat(b, p, 0, p->len);
|
|
string_buffer_write8(b, (const uint8_t *)str3, len3);
|
|
|
|
JS_FreeValue(ctx, str2);
|
|
return string_buffer_end(b);
|
|
|
|
fail:
|
|
JS_FreeValue(ctx, str2);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue JS_NewString(JSContext *ctx, const char *str)
|
|
{
|
|
return JS_NewStringLen(ctx, str, strlen(str));
|
|
}
|
|
|
|
JSValue JS_NewAtomString(JSContext *ctx, const char *str)
|
|
{
|
|
JSAtom atom = JS_NewAtom(ctx, str);
|
|
if (atom == JS_ATOM_NULL)
|
|
return JS_EXCEPTION;
|
|
JSValue val = JS_AtomToString(ctx, atom);
|
|
JS_FreeAtom(ctx, atom);
|
|
return val;
|
|
}
|
|
|
|
/* return (NULL, 0) if exception. */
|
|
/* return pointer into a JSString with a live ref_count */
|
|
/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */
|
|
const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, BOOL cesu8)
|
|
{
|
|
JSValue val;
|
|
JSString *str, *str_new;
|
|
int pos, len, c, c1;
|
|
uint8_t *q;
|
|
|
|
if (JS_VALUE_GET_TAG(val1) != JS_TAG_STRING) {
|
|
val = JS_ToString(ctx, val1);
|
|
if (JS_IsException(val))
|
|
goto fail;
|
|
} else {
|
|